Seam SVN: r12914 - in modules/security/trunk: impl/src/main/java/org/jboss/seam/security/management and 1 other directory.
by seam-commits@lists.jboss.org
Author: shane.bryzak(a)jboss.com
Date: 2010-05-31 19:37:43 -0400 (Mon, 31 May 2010)
New Revision: 12914
Removed:
modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/PasswordEncoder.java
Modified:
modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityManager.java
modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityStore.java
modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/IdentityManagerImpl.java
modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java
modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/PasswordHashEncoder.java
Log:
update IdentityManager api
Modified: modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityManager.java
===================================================================
--- modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityManager.java 2010-05-31 23:30:39 UTC (rev 12913)
+++ modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityManager.java 2010-05-31 23:37:43 UTC (rev 12914)
@@ -149,26 +149,48 @@
boolean revokeRole(String username, String roleType, String groupName, String groupType);
/**
- * Adds a user to the specified group
+ * Associates a user with the specified group
*
- * @param username The username of the user being added to the group
* @param groupName The name of the group the user is being added to
* @param groupType The type of the group
- * @return true if the user was successfully added
+ * @param username The username of the user being added to the group
+ * @return true if the user was successfully associated
*/
- boolean addUserToGroup(String username, String groupName, String groupType);
+ boolean associateUser(String groupName, String groupType, String username);
/**
- * Removes a user from the specified group
+ * Disassociates a user with the specified group
*
- * @param username The username of the user being removed
* @param groupName The name of the group the user is being removed from
* @param groupType The type of the group
- * @return true if the user was successfully removed
+ * @param username The username of the user being removed
+ * @return true if the user was successfully disassociated
*/
- boolean removeUserFromGroup(String username, String groupName, String groupType);
+ boolean disassociateUser(String groupName, String groupType, String username);
/**
+ * Associates a group with the specified "parent" group
+ *
+ * @param groupName The name of the parent group
+ * @param groupType The type of the parent group
+ * @param memberGroupName The name of the member group
+ * @param memberGroupType The type of the member group
+ * @return true if the group was successfully associated
+ */
+ boolean associateGroup(String groupName, String groupType, String memberGroupName, String memberGroupType);
+
+ /**
+ * Disassociates a member group with the specified "parent" group
+ *
+ * @param groupNameThe name of the parent group
+ * @param groupType The type of the parent group
+ * @param memberGroupName The name of the member group
+ * @param memberGroupType The type of the member group
+ * @return true if the group was successfully disassociated
+ */
+ boolean disassociateGroup(String groupName, String groupType, String memberGroupName, String memberGroupType);
+
+ /**
* Finds users that match the specified filter. A filter of null will return
* all users.
*
Modified: modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityStore.java
===================================================================
--- modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityStore.java 2010-05-31 23:30:39 UTC (rev 12913)
+++ modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityStore.java 2010-05-31 23:37:43 UTC (rev 12914)
@@ -139,24 +139,6 @@
boolean deleteUserAttribute(String username, String attribute);
/**
- *
- * @param username
- * @param groupName
- * @param groupType
- * @return
- */
- boolean addUserToGroup(String username, String groupName, String groupType);
-
- /**
- *
- * @param username
- * @param groupName
- * @param groupType
- * @return
- */
- boolean removeUserFromGroup(String username, String groupName, String groupType);
-
- /**
* Creates a new role type with the specified role type name.
* @return true if the role type was created successfully.
*/
@@ -203,24 +185,14 @@
*/
boolean createGroup(String name, String groupType);
- /**
- * Add the specified user to the specified group
- *
- * @param user The name of the user
- * @param group The name of the group
- * @return true if the user was successfully added
- */
- boolean addUserToGroup(String username, Group group);
+ boolean associateUser(String groupName, String groupType, String username);
- /**
- * Removes the specified user from the specified group
- *
- * @param user The user to remove
- * @param group The group to remove the user from
- * @return true if the user was successfully removed
- */
- boolean removeUserFromGroup(String username, Group group);
+ boolean disassociateUser(String groupName, String groupType, String username);
+ boolean associateGroup(String groupName, String groupType, String memberGroupName, String memberGroupType);
+
+ boolean disassociateGroup(String groupName, String groupType, String memberGroupName, String memberGroupType);
+
/**
* Deletes the specified group
*
Deleted: modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/PasswordEncoder.java
===================================================================
--- modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/PasswordEncoder.java 2010-05-31 23:30:39 UTC (rev 12913)
+++ modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/PasswordEncoder.java 2010-05-31 23:37:43 UTC (rev 12914)
@@ -1,29 +0,0 @@
-package org.jboss.seam.security.management;
-
-import java.util.Map;
-
-/**
- * Encodes user passwords for persistent storage
- *
- * @author Shane Bryzak
- *
- */
-public interface PasswordEncoder
-{
- /**
- * Encodes the specified password
- *
- * @param password
- * @return
- */
- String encodePassword(String password);
-
- /**
- * Encodes the specified password using the provided options
- *
- * @param password
- * @param options
- * @return
- */
- String encodePassword(String password, Map options);
-}
Modified: modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/IdentityManagerImpl.java
===================================================================
--- modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/IdentityManagerImpl.java 2010-05-31 23:30:39 UTC (rev 12913)
+++ modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/IdentityManagerImpl.java 2010-05-31 23:37:43 UTC (rev 12914)
@@ -127,20 +127,30 @@
{
identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_UPDATE);
return roleIdentityStore.revokeRole(name, role, groupName, groupType);
+ }
+
+ public boolean associateUser(String groupName, String groupType, String username)
+ {
+ identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_UPDATE);
+ return identityStore.associateUser(groupName, groupType, username);
}
- public boolean addUserToGroup(String username, String groupName, String groupType)
+ public boolean disassociateUser(String groupName, String groupType, String username)
{
identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_UPDATE);
- return identityStore.addUserToGroup(username, groupName, groupType);
+ return identityStore.disassociateUser(groupName, groupType, username);
}
- public boolean removeUserFromGroup(String username, String groupName, String groupType)
+ public boolean associateGroup(String groupName, String groupType, String memberGroupName, String memberGroupType)
{
- identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_UPDATE);
- return identityStore.removeUserFromGroup(username, groupName, groupType);
+ return false;
}
+ public boolean disassociateGroup(String groupName, String groupType, String memberGroupName, String memberGroupType)
+ {
+ return false;
+ }
+
public boolean createRoleType(String roleType)
{
identity.checkPermission(ROLE_PERMISSION_NAME, PERMISSION_CREATE);
Modified: modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java
===================================================================
--- modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java 2010-05-31 23:30:39 UTC (rev 12913)
+++ modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java 2010-05-31 23:37:43 UTC (rev 12914)
@@ -1,6 +1,7 @@
package org.jboss.seam.security.management;
import java.io.Serializable;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -12,6 +13,7 @@
import org.jboss.seam.security.annotations.management.IdentityProperty;
import org.jboss.seam.security.annotations.management.PropertyType;
+import org.jboss.weld.extensions.util.AbstractBeanProperty;
import org.jboss.weld.extensions.util.AnnotatedBeanProperty;
import org.jboss.weld.extensions.util.TypedBeanProperty;
import org.picketlink.idm.api.Credential;
@@ -37,6 +39,9 @@
private static final String DEFAULT_RELATIONSHIP_TYPE_MEMBERSHIP = "MEMBERSHIP";
private static final String DEFAULT_RELATIONSHIP_TYPE_ROLE = "ROLE";
+
+ private static final String DEFAULT_ATTRIBUTE_USER_ENABLED = "ENABLED";
+ private static final String DEFAULT_ATTRIBUTE_PASSWORD_SALT = "PASSWORD_SALT";
private Logger log = LoggerFactory.getLogger(JpaIdentityStore.class);
@@ -72,6 +77,9 @@
private AnnotatedBeanProperty<IdentityProperty> roleTypeNameProperty;
+ private Map<String,AnnotatedBeanProperty<IdentityProperty>> annotatedProperties =
+ new HashMap<String,AnnotatedBeanProperty<IdentityProperty>>();
+
private String userIdentityType = DEFAULT_USER_IDENTITY_TYPE;
private String roleIdentityType = DEFAULT_ROLE_IDENTITY_TYPE;
private String groupIdentityType = DEFAULT_GROUP_IDENTITY_TYPE;
@@ -273,7 +281,7 @@
/**
*
*/
- @Inject PasswordEncoder passwordEncoder;
+ @Inject CredentialEncoder credentialEncoder;
public boolean createUser(String username, Credential credential,
Map<String, ?> attributes)
@@ -370,18 +378,25 @@
}
}
- public boolean addUserToGroup(String username, String groupName,
- String groupType)
+ public boolean associateUser(String groupName, String groupType, String username)
{
- // TODO Auto-generated method stub
return false;
}
-
- public boolean addUserToGroup(String username, Group group)
+
+ public boolean disassociateUser(String groupName, String groupType, String username)
{
- // TODO Auto-generated method stub
return false;
}
+
+ public boolean associateGroup(String groupName, String groupType, String memberGroupName, String memberGroupType)
+ {
+ return false;
+ }
+
+ public boolean disassociateGroup(String groupName, String groupType, String memberGroupName, String memberGroupType)
+ {
+ return false;
+ }
public boolean authenticate(String username, Credential credential)
{
@@ -499,19 +514,6 @@
return null;
}
- public boolean removeUserFromGroup(String username, String groupName,
- String groupType)
- {
- // TODO Auto-generated method stub
- return false;
- }
-
- public boolean removeUserFromGroup(String username, Group group)
- {
- // TODO Auto-generated method stub
- return false;
- }
-
public boolean revokeRole(String username, String roleType,
String groupName, String groupType)
{
Modified: modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/PasswordHashEncoder.java
===================================================================
--- modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/PasswordHashEncoder.java 2010-05-31 23:30:39 UTC (rev 12913)
+++ modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/PasswordHashEncoder.java 2010-05-31 23:37:43 UTC (rev 12914)
@@ -2,13 +2,15 @@
import java.util.Map;
+import org.picketlink.idm.api.Credential;
+
/**
* Default password encoder, creates password hashes.
*
* @author Shane Bryzak
*
*/
-public class PasswordHashEncoder implements PasswordEncoder
+public class PasswordHashEncoder implements CredentialEncoder
{
private String passwordHash;
private int passwordIterations = 1000;
@@ -32,14 +34,11 @@
{
this.passwordIterations = passwordIterations;
}
-
- public String encodePassword(String password)
+
+ public String encode(Credential credential, Map<String,Object> options)
{
- return encodePassword(password, null);
- }
-
- public String encodePassword(String password, Map options)
- {
+
+ // TODO Auto-generated method stub
return null;
}
}
14 years, 7 months
Seam SVN: r12913 - branches/community/Seam_2_2/src/main/org/jboss/seam/document.
by seam-commits@lists.jboss.org
Author: manaRH
Date: 2010-05-31 19:30:39 -0400 (Mon, 31 May 2010)
New Revision: 12913
Modified:
branches/community/Seam_2_2/src/main/org/jboss/seam/document/DocumentStore.java
Log:
JBSEAM-4626 - enable install of custom DocumentStore
Modified: branches/community/Seam_2_2/src/main/org/jboss/seam/document/DocumentStore.java
===================================================================
--- branches/community/Seam_2_2/src/main/org/jboss/seam/document/DocumentStore.java 2010-05-31 23:21:53 UTC (rev 12912)
+++ branches/community/Seam_2_2/src/main/org/jboss/seam/document/DocumentStore.java 2010-05-31 23:30:39 UTC (rev 12913)
@@ -69,7 +69,7 @@
public static DocumentStore instance()
{
- return (DocumentStore) Component.getInstance(DocumentStore.class);
+ return (DocumentStore) Component.getInstance("org.jboss.seam.document.documentStore");
}
14 years, 7 months
Seam SVN: r12912 - in branches/community/Seam_2_2: src/main/org/jboss/seam/transaction and 1 other directory.
by seam-commits@lists.jboss.org
Author: manaRH
Date: 2010-05-31 19:21:53 -0400 (Mon, 31 May 2010)
New Revision: 12912
Modified:
branches/community/Seam_2_2/examples/restbay/src/org/jboss/seam/example/restbay/test/testng.xml
branches/community/Seam_2_2/src/main/org/jboss/seam/transaction/EjbSynchronizations.java
Log:
JBSEAM-4542
Modified: branches/community/Seam_2_2/examples/restbay/src/org/jboss/seam/example/restbay/test/testng.xml
===================================================================
--- branches/community/Seam_2_2/examples/restbay/src/org/jboss/seam/example/restbay/test/testng.xml 2010-05-31 21:19:17 UTC (rev 12911)
+++ branches/community/Seam_2_2/examples/restbay/src/org/jboss/seam/example/restbay/test/testng.xml 2010-05-31 23:21:53 UTC (rev 12912)
@@ -28,11 +28,7 @@
<test name="RestBay Security">
<classes>
- <class name="org.jboss.seam.example.restbay.test.SecurityTest">
- <methods>
- <exclude name="synchronizationsLookup"/><!-- JBSEAM-4542 -->
- </methods>
- </class>
+ <class name="org.jboss.seam.example.restbay.test.SecurityTest"/>
</classes>
</test>
Modified: branches/community/Seam_2_2/src/main/org/jboss/seam/transaction/EjbSynchronizations.java
===================================================================
--- branches/community/Seam_2_2/src/main/org/jboss/seam/transaction/EjbSynchronizations.java 2010-05-31 21:19:17 UTC (rev 12911)
+++ branches/community/Seam_2_2/src/main/org/jboss/seam/transaction/EjbSynchronizations.java 2010-05-31 23:21:53 UTC (rev 12912)
@@ -34,7 +34,7 @@
*/
@Stateful
@Name("org.jboss.seam.transaction.synchronizations")
-(a)Scope(ScopeType.EVENT)
+(a)Scope(ScopeType.SESSION)
@Install(precedence=FRAMEWORK, dependencies="org.jboss.seam.transaction.ejbTransaction")
@BypassInterceptors
@TransactionAttribute(TransactionAttributeType.REQUIRED)
14 years, 7 months
Seam SVN: r12911 - in modules/international/trunk: docs and 5 other directories.
by seam-commits@lists.jboss.org
Author: kenfinni
Date: 2010-05-31 17:19:17 -0400 (Mon, 31 May 2010)
New Revision: 12911
Added:
modules/international/trunk/docs/reference/
modules/international/trunk/docs/reference/pom.xml
modules/international/trunk/docs/reference/src/
modules/international/trunk/docs/reference/src/main/
modules/international/trunk/docs/reference/src/main/docbook/
modules/international/trunk/docs/reference/src/main/docbook/en-US/
modules/international/trunk/docs/reference/src/main/docbook/en-US/author_group.xml
modules/international/trunk/docs/reference/src/main/docbook/en-US/book_info.xml
modules/international/trunk/docs/reference/src/main/docbook/en-US/installation.xml
modules/international/trunk/docs/reference/src/main/docbook/en-US/locales.xml
modules/international/trunk/docs/reference/src/main/docbook/en-US/master.xml
modules/international/trunk/docs/reference/src/main/docbook/en-US/messages.xml
modules/international/trunk/docs/reference/src/main/docbook/en-US/preface.xml
modules/international/trunk/docs/reference/src/main/docbook/en-US/timezones.xml
Modified:
modules/international/trunk/pom.xml
Log:
Structure for International reference doc
Added: modules/international/trunk/docs/reference/pom.xml
===================================================================
--- modules/international/trunk/docs/reference/pom.xml (rev 0)
+++ modules/international/trunk/docs/reference/pom.xml 2010-05-31 21:19:17 UTC (rev 12911)
@@ -0,0 +1,53 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.jboss.seam.international</groupId>
+ <artifactId>seam-international-reference-guide</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ <packaging>jdocbook</packaging>
+ <name>Seam International Module Reference Guide</name>
+
+ <parent>
+ <groupId>org.jboss.weld</groupId>
+ <artifactId>weld-parent</artifactId>
+ <version>10-SNAPSHOT</version>
+ </parent>
+
+ <properties>
+ <pdf.name>${project.artifactId}.pdf</pdf.name>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.jboss.maven.plugins</groupId>
+ <artifactId>maven-jdocbook-plugin</artifactId>
+ </plugin>
+ <!-- Attach docs as a war so dist can retrieve them -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-zip</id>
+ <phase>package</phase>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.outputDirectory}/${project.artifactId}-${project.version}.war</file>
+ <type>war</type>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
Added: modules/international/trunk/docs/reference/src/main/docbook/en-US/author_group.xml
===================================================================
--- modules/international/trunk/docs/reference/src/main/docbook/en-US/author_group.xml (rev 0)
+++ modules/international/trunk/docs/reference/src/main/docbook/en-US/author_group.xml 2010-05-31 21:19:17 UTC (rev 12911)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE bookinfo PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" []>
+<authorgroup>
+ <author>
+ <firstname>Ken</firstname>
+ <surname>Finnigan</surname>
+ </author>
+ <author>
+ <firstname>Lincoln</firstname>
+ <surname>Baxter III</surname>
+ </author>
+
+ <!--
+vim:et:ts=3:sw=3:tw=120
+-->
+</authorgroup>
Added: modules/international/trunk/docs/reference/src/main/docbook/en-US/book_info.xml
===================================================================
--- modules/international/trunk/docs/reference/src/main/docbook/en-US/book_info.xml (rev 0)
+++ modules/international/trunk/docs/reference/src/main/docbook/en-US/book_info.xml 2010-05-31 21:19:17 UTC (rev 12911)
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE bookinfo PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" []>
+
+<bookinfo>
+ <title>Seam International Module</title>
+ <subtitle>Reference Guide</subtitle>
+ <releaseinfo>3.0.0-SNAPSHOT</releaseinfo>
+
+ <xi:include href="author_group.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
+
+<!--
+vim:et:ts=3:sw=3:tw=120
+-->
+</bookinfo>
Added: modules/international/trunk/docs/reference/src/main/docbook/en-US/installation.xml
===================================================================
--- modules/international/trunk/docs/reference/src/main/docbook/en-US/installation.xml (rev 0)
+++ modules/international/trunk/docs/reference/src/main/docbook/en-US/installation.xml 2010-05-31 21:19:17 UTC (rev 12911)
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" []>
+<chapter id="installation">
+ <title>Installation</title>
+ <para>
+ Most features of Seam International are installed automatically by including the seam-international.jar and seam-international-api.jar
+ in the web application library folder. If you are using <ulink url="http://maven.apache.org/">Maven</ulink> as your build tool, you can add the
+ following dependency to your pom.xml file:
+ </para>
+ <programlisting role="XML"><![CDATA[<dependency>
+ <groupId>org.jboss.seam</groupId>
+ <artifactId>seam-international</artifactId>
+ <version>${seam-international-version}</version>
+</dependency>]]></programlisting>
+ <tip>
+ <para>Replace ${seam-international-version} with the most recent or appropriate version of Seam International.</para>
+ </tip>
+
+</chapter>
\ No newline at end of file
Added: modules/international/trunk/docs/reference/src/main/docbook/en-US/locales.xml
===================================================================
--- modules/international/trunk/docs/reference/src/main/docbook/en-US/locales.xml (rev 0)
+++ modules/international/trunk/docs/reference/src/main/docbook/en-US/locales.xml 2010-05-31 21:19:17 UTC (rev 12911)
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" []>
+<chapter id="locales">
+ <title>Locales</title>
+</chapter>
\ No newline at end of file
Added: modules/international/trunk/docs/reference/src/main/docbook/en-US/master.xml
===================================================================
--- modules/international/trunk/docs/reference/src/main/docbook/en-US/master.xml (rev 0)
+++ modules/international/trunk/docs/reference/src/main/docbook/en-US/master.xml 2010-05-31 21:19:17 UTC (rev 12911)
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" []>
+<book lang="en">
+
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="book_info.xml" />
+
+ <toc />
+
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="preface.xml" />
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="installation.xml" />
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="locales.xml" />
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="timezones.xml" />
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="messages.xml" />
+
+ <!--
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="chapterXX.xml" />
+ -->
+
+<!--
+vim:et:ts=3:sw=3:tw=120
+-->
+</book>
Added: modules/international/trunk/docs/reference/src/main/docbook/en-US/messages.xml
===================================================================
--- modules/international/trunk/docs/reference/src/main/docbook/en-US/messages.xml (rev 0)
+++ modules/international/trunk/docs/reference/src/main/docbook/en-US/messages.xml 2010-05-31 21:19:17 UTC (rev 12911)
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" []>
+<chapter id="messages">
+ <title>Messages</title>
+</chapter>
\ No newline at end of file
Added: modules/international/trunk/docs/reference/src/main/docbook/en-US/preface.xml
===================================================================
--- modules/international/trunk/docs/reference/src/main/docbook/en-US/preface.xml (rev 0)
+++ modules/international/trunk/docs/reference/src/main/docbook/en-US/preface.xml 2010-05-31 21:19:17 UTC (rev 12911)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" []>
+<preface>
+ <title>Introduction</title>
+ <para>
+ The goal of Seam International is to provide a unified approach to configuring locale, timezone and language.
+ With features such as Status messages propogation to UI, multiple property storage implementations and more.
+ </para>
+
+<!--
+vim:et:ts=3:sw=3:tw=120
+-->
+</preface>
Added: modules/international/trunk/docs/reference/src/main/docbook/en-US/timezones.xml
===================================================================
--- modules/international/trunk/docs/reference/src/main/docbook/en-US/timezones.xml (rev 0)
+++ modules/international/trunk/docs/reference/src/main/docbook/en-US/timezones.xml 2010-05-31 21:19:17 UTC (rev 12911)
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" []>
+<chapter id="timezones">
+ <title>Timezones</title>
+</chapter>
\ No newline at end of file
Modified: modules/international/trunk/pom.xml
===================================================================
--- modules/international/trunk/pom.xml 2010-05-31 03:21:25 UTC (rev 12910)
+++ modules/international/trunk/pom.xml 2010-05-31 21:19:17 UTC (rev 12911)
@@ -126,6 +126,20 @@
<scope>test</scope>
</dependency>
</dependencies>
+ <profiles>
+ <profile>
+ <id>distribution</id>
+ <activation>
+ <property>
+ <name>dist</name>
+ </property>
+ </activation>
+ <modules>
+ <module>dist</module>
+ <module>docs/reference</module>
+ </modules>
+ </profile>
+ </profiles>
<developers>
<developer>
14 years, 7 months
Seam SVN: r12910 - in modules/security/trunk: impl/src/main/java/org/jboss/seam/security/management and 1 other directory.
by seam-commits@lists.jboss.org
Author: shane.bryzak(a)jboss.com
Date: 2010-05-30 23:21:25 -0400 (Sun, 30 May 2010)
New Revision: 12910
Modified:
modules/security/trunk/api/src/main/java/org/jboss/seam/security/annotations/management/PropertyType.java
modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java
Log:
basic implementation for createUser()
Modified: modules/security/trunk/api/src/main/java/org/jboss/seam/security/annotations/management/PropertyType.java
===================================================================
--- modules/security/trunk/api/src/main/java/org/jboss/seam/security/annotations/management/PropertyType.java 2010-05-30 23:53:42 UTC (rev 12909)
+++ modules/security/trunk/api/src/main/java/org/jboss/seam/security/annotations/management/PropertyType.java 2010-05-31 03:21:25 UTC (rev 12910)
@@ -5,5 +5,6 @@
* @author Shane Bryzak
*/
public enum PropertyType {
- NAME, TYPE, VALUE, RELATIONSHIP_FROM, RELATIONSHIP_TO
+ NAME, TYPE, VALUE, RELATIONSHIP_FROM, RELATIONSHIP_TO, CREDENTIAL,
+ CREDENTIAL_TYPE
}
Modified: modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java
===================================================================
--- modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java 2010-05-30 23:53:42 UTC (rev 12909)
+++ modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java 2010-05-31 03:21:25 UTC (rev 12910)
@@ -8,10 +8,12 @@
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
import org.jboss.seam.security.annotations.management.IdentityProperty;
import org.jboss.seam.security.annotations.management.PropertyType;
import org.jboss.weld.extensions.util.AnnotatedBeanProperty;
+import org.jboss.weld.extensions.util.TypedBeanProperty;
import org.picketlink.idm.api.Credential;
import org.picketlink.idm.api.Group;
import org.picketlink.idm.api.IdentityType;
@@ -48,22 +50,26 @@
// The following entity classes may be determined automatically
private Class<?> identityTypeEntity;
private Class<?> relationshipTypeEntity;
- private Class<?> credentialTypeEntity;
+ private Class<?> credentialTypeEntity;
-
private AnnotatedBeanProperty<IdentityProperty> identityNameProperty;
private AnnotatedBeanProperty<IdentityProperty> identityTypeProperty;
private AnnotatedBeanProperty<IdentityProperty> identityTypeNameProperty;
+
private AnnotatedBeanProperty<IdentityProperty> relationshipNameProperty;
private AnnotatedBeanProperty<IdentityProperty> relationshipTypeProperty;
private AnnotatedBeanProperty<IdentityProperty> relationshipFromProperty;
private AnnotatedBeanProperty<IdentityProperty> relationshipToProperty;
private AnnotatedBeanProperty<IdentityProperty> relationshipTypeNameProperty;
+
private AnnotatedBeanProperty<IdentityProperty> credentialTypeProperty;
private AnnotatedBeanProperty<IdentityProperty> credentialValueProperty;
private AnnotatedBeanProperty<IdentityProperty> credentialTypeNameProperty;
+ private TypedBeanProperty credentialIdentityProperty;
+
private AnnotatedBeanProperty<IdentityProperty> attributeNameProperty;
private AnnotatedBeanProperty<IdentityProperty> attributeValueProperty;
+
private AnnotatedBeanProperty<IdentityProperty> roleTypeNameProperty;
private String userIdentityType = DEFAULT_USER_IDENTITY_TYPE;
@@ -77,13 +83,13 @@
{
private PropertyType pt;
- public EntityProperty(Class<?> cls, Class<IdentityProperty> annotationClass, PropertyType pt)
+ public EntityProperty(Class<?> cls, PropertyType pt)
{
- super(cls, annotationClass);
+ super(cls, IdentityProperty.class);
this.pt = pt;
}
- public boolean isMatch(IdentityProperty p)
+ public boolean annotationMatches(IdentityProperty p)
{
return p.value().equals(pt);
}
@@ -104,64 +110,59 @@
"Error initializing JpaIdentityStore - identityObjectRelationshipEntity not set");
}
- identityNameProperty = new EntityProperty(identityObjectEntity,
- IdentityProperty.class, PropertyType.NAME);
+ identityNameProperty = new EntityProperty(identityObjectEntity, PropertyType.NAME);
- identityTypeProperty = new EntityProperty(identityObjectEntity,
- IdentityProperty.class, PropertyType.TYPE);
+ identityTypeProperty = new EntityProperty(identityObjectEntity, PropertyType.TYPE);
if (!String.class.equals(identityTypeProperty.getPropertyType()))
{
// If the identity type property isn't a String, it must be a related entity
identityTypeEntity = (Class<?>) identityTypeProperty.getPropertyType();
- identityTypeNameProperty = new EntityProperty(identityTypeEntity,
- IdentityProperty.class, PropertyType.NAME);
+ identityTypeNameProperty = new EntityProperty(identityTypeEntity, PropertyType.NAME);
}
- relationshipNameProperty = new EntityProperty(relationshipEntity,
- IdentityProperty.class, PropertyType.NAME);
+ relationshipNameProperty = new EntityProperty(relationshipEntity, PropertyType.NAME);
- relationshipFromProperty = new EntityProperty(relationshipEntity,
- IdentityProperty.class, PropertyType.RELATIONSHIP_FROM);
+ relationshipFromProperty = new EntityProperty(relationshipEntity, PropertyType.RELATIONSHIP_FROM);
- relationshipToProperty = new EntityProperty(relationshipEntity,
- IdentityProperty.class, PropertyType.RELATIONSHIP_TO);
+ relationshipToProperty = new EntityProperty(relationshipEntity, PropertyType.RELATIONSHIP_TO);
- relationshipTypeProperty = new EntityProperty(relationshipEntity,
- IdentityProperty.class, PropertyType.TYPE);
+ relationshipTypeProperty = new EntityProperty(relationshipEntity, PropertyType.TYPE);
if (!String.class.equals(relationshipTypeProperty.getPropertyType()))
{
relationshipTypeEntity = (Class<?>) relationshipTypeProperty.getPropertyType();
- relationshipTypeNameProperty = new EntityProperty(relationshipTypeEntity,
- IdentityProperty.class, PropertyType.NAME);
+ relationshipTypeNameProperty = new EntityProperty(relationshipTypeEntity, PropertyType.NAME);
}
// If a credential entity has been configured, scan it
if (credentialEntity != null)
{
- credentialTypeProperty = new EntityProperty(credentialEntity,
- IdentityProperty.class, PropertyType.TYPE);
+ credentialTypeProperty = new EntityProperty(credentialEntity, PropertyType.TYPE);
+ // If the credential type property isn't a string, assume the credential type
+ // is in another table
if (!String.class.equals(credentialTypeProperty.getPropertyType()))
{
credentialTypeEntity = (Class<?>) credentialTypeProperty.getPropertyType();
- credentialTypeNameProperty = new EntityProperty(credentialTypeEntity,
- IdentityProperty.class, PropertyType.NAME);
+ credentialTypeNameProperty = new EntityProperty(credentialTypeEntity, PropertyType.NAME);
+
+ credentialIdentityProperty = new TypedBeanProperty(credentialTypeEntity, identityObjectEntity);
}
- credentialValueProperty = new EntityProperty(credentialEntity,
- IdentityProperty.class, PropertyType.VALUE);
+ credentialValueProperty = new EntityProperty(credentialEntity, PropertyType.VALUE);
+
+
}
- // otherwise assume that the credential value is stored in the identityObjectEntity
else
{
- // TODO implement this, we'll probably need some new PropertyType enums to support it
+ // otherwise assume that the credential value is stored in the identityObjectEntity
+ credentialTypeProperty = new EntityProperty(identityObjectEntity, PropertyType.CREDENTIAL_TYPE);
+ credentialValueProperty = new EntityProperty(identityObjectEntity, PropertyType.CREDENTIAL);
}
-
-
+ roleTypeNameProperty = new EntityProperty(roleTypeEntity, PropertyType.NAME);
}
public Class<?> getIdentityObjectEntity()
@@ -174,44 +175,44 @@
this.identityObjectEntity = identityObjectEntity;
}
- public Class<?> getIdentityObjectRelationshipEntity()
+ public Class<?> getRelationshipEntity()
{
return relationshipEntity;
}
- public void setIdentityObjectRelationshipEntity(Class<?> identityObjectRelationshipEntity)
+ public void setRelationshipEntity(Class<?> relationshipEntity)
{
- this.relationshipEntity = identityObjectRelationshipEntity;
+ this.relationshipEntity = relationshipEntity;
}
- public Class<?> getIdentityObjectCredentialEntity()
+ public Class<?> getCredentialEntity()
{
return credentialEntity;
}
- public void setIdentityObjectCredentialEntity(Class<?> identityObjectCredentialEntity)
+ public void setCredentialEntity(Class<?> credentialEntity)
{
- this.credentialEntity = identityObjectCredentialEntity;
+ this.credentialEntity = credentialEntity;
}
- public Class<?> getIdentityObjectAttributeEntity()
+ public Class<?> getAttributeEntity()
{
return attributeEntity;
}
- public void setIdentityObjectAttributeEntity(Class<?> identityObjectAttributeEntity)
+ public void setAttributeEntity(Class<?> attributeEntity)
{
- this.attributeEntity = identityObjectAttributeEntity;
+ this.attributeEntity = attributeEntity;
}
- public Class<?> getIdentityRoleTypeEntity()
+ public Class<?> getRoleTypeEntity()
{
return roleTypeEntity;
}
- public void setIdentityRoleTypeEntity(Class<?> identityRoleTypeEntity)
+ public void setRoleTypeEntity(Class<?> roleTypeEntity)
{
- this.roleTypeEntity = identityRoleTypeEntity;
+ this.roleTypeEntity = roleTypeEntity;
}
public String getUserIdentityType()
@@ -273,7 +274,102 @@
*
*/
@Inject PasswordEncoder passwordEncoder;
+
+ public boolean createUser(String username, Credential credential,
+ Map<String, ?> attributes)
+ {
+ try
+ {
+ if (getIdentityObjectEntity() == null)
+ {
+ throw new IdentityManagementException("Could not create user, identityObjectEntity not set.");
+ }
+
+ if (userExists(username))
+ {
+ log.warn("Could not create user, already exists.");
+ }
+
+ Object userInstance = getIdentityObjectEntity().newInstance();
+ Object credentialInstance = null;
+
+ identityNameProperty.setValue(userInstance, username);
+
+ if (String.class.equals(identityTypeProperty.getPropertyType()))
+ {
+ identityTypeProperty.setValue(userInstance, userIdentityType);
+ }
+ else
+ {
+ identityTypeProperty.setValue(userInstance, lookupIdentityType(userIdentityType));
+ }
+
+ if (getCredentialEntity() == null)
+ {
+ // The credential must be stored in the identity object
+ if (credentialValueProperty.isValid())
+ {
+ // TODO need to abstract this out
+ credentialValueProperty.setValue(userInstance, credential);
+ }
+ }
+ else
+ {
+ credentialInstance = getCredentialEntity().newInstance();
+ credentialIdentityProperty.setValue(credentialInstance, userInstance);
+
+ // TODO need to abstract this out
+ credentialValueProperty.setValue(credentialInstance, credential);
+ if (credentialTypeProperty.isValid())
+ {
+ // TODO set the credential type - need some kind of mapper?
+ //credentialTypeProperty.setValue(credentialInstance, lookupCredentialType)
+ }
+ }
+
+ // TODO create attributes
+
+ entityManagerInstance.get().persist(userInstance);
+
+ if (credentialInstance != null)
+ {
+ entityManagerInstance.get().persist(credentialInstance);
+ }
+ }
+ catch (Exception ex)
+ {
+ if (ex instanceof IdentityManagementException)
+ {
+ throw (IdentityManagementException) ex;
+ }
+ else
+ {
+ throw new IdentityManagementException("Could not create user.", ex);
+ }
+ }
+
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ private Object lookupIdentityType(String identityType)
+ {
+ try
+ {
+ Object val = entityManagerInstance.get().createQuery(
+ "select t from " + identityTypeEntity.getName() + " t where t." +
+ identityTypeNameProperty.getName() + " = :identityType")
+ .setParameter("identityType", identityType)
+ .getSingleResult();
+ return val;
+ }
+ catch (NoResultException ex)
+ {
+ return null;
+ }
+ }
+
public boolean addUserToGroup(String username, String groupName,
String groupType)
{
@@ -305,13 +401,6 @@
return false;
}
- public boolean createUser(String username, Credential credential,
- Map<String, ?> attributes)
- {
- // TODO Auto-generated method stub
- return false;
- }
-
public boolean deleteGroup(String name, String groupType)
{
// TODO Auto-generated method stub
@@ -461,6 +550,4 @@
return false;
}
-
-
}
14 years, 7 months
Seam SVN: r12909 - modules/security/trunk/docs/en-US.
by seam-commits@lists.jboss.org
Author: shane.bryzak(a)jboss.com
Date: 2010-05-30 19:53:42 -0400 (Sun, 30 May 2010)
New Revision: 12909
Added:
modules/security/trunk/docs/en-US/images/
modules/security/trunk/docs/en-US/security-general.xml
Log:
re-add old documentation
Added: modules/security/trunk/docs/en-US/security-general.xml
===================================================================
--- modules/security/trunk/docs/en-US/security-general.xml (rev 0)
+++ modules/security/trunk/docs/en-US/security-general.xml 2010-05-30 23:53:42 UTC (rev 12909)
@@ -0,0 +1,5202 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN" "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd">
+
+<chapter id="security">
+ <title>Security</title>
+
+ <sect1>
+ <title>Overview</title>
+
+ <para>
+ The Seam Security API provides a multitude of security-related features for your Seam-based application, covering
+ such areas as:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Authentication - an extensible, JAAS-based authentication layer that allows users to authenticate
+ against any security provider.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Identity Management - an API for managing a Seam application's users and roles at runtime.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Authorization - an extremely comprehensive authorization framework, supporting user roles, persistent and
+ rule-based permissions, and a pluggable permission resolver for easily implementing customised security logic.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Permission Management - a set of built-in Seam components to allow easy management of an application's
+ security policy.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ CAPTCHA support - to assist in the prevention of automated software/scripts abusing your Seam-based site.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ And much more
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ This chapter will cover each of these features in detail.
+ </para>
+
+ </sect1>
+
+ <sect1>
+ <title>Disabling Security</title>
+
+ <para>
+ In some situations it may be necessary to disable Seam Security, for instances during unit tests or because you
+ are using a different approach to security, such as native JAAS. Simply call the static method
+ <literal>Identity.setSecurityEnabled(false)</literal> to disable the security infrastructure. Of course, it's not
+ very convenient to have to call a static method when you want to configure the application, so as an alternative
+ you can control this setting in components.xml:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>Entity Security</para>
+ </listitem>
+ <listitem>
+ <para>Hibernate Security Interceptor</para>
+ </listitem>
+ <listitem>
+ <para>Seam Security Interceptor</para>
+ </listitem>
+ <listitem>
+ <para>Page restrictions</para>
+ </listitem>
+ <listitem>
+ <para>Servlet API security integration</para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ Assuming you are planning to take advantage of what Seam Security has to offer, the rest of this chapter documents
+ the plethora of options you have for giving your user an identity in the eyes of the security model
+ (authentication) and locking down the application by establishing constraints (authorization). Let's begin with
+ the task of authentication since that's the foundation of any security model.
+ </para>
+
+ </sect1>
+
+ <sect1>
+ <title>Authentication</title>
+
+ <para>
+ The authentication features provided by Seam Security are built upon JAAS (Java Authentication and Authorization Service),
+ and as such provide a robust and highly configurable API for handling user authentication. However, for less complex
+ authentication requirements Seam offers a much more simplified method of authentication that hides the complexity of JAAS.
+ </para>
+
+ <sect2>
+ <title>Configuring an Authenticator component</title>
+
+ <note>
+ <para>
+ If you use Seam's Identity Management features (discussed later in this chapter) then it is not necessary to create
+ an authenticator component (and you can skip this section).
+ </para>
+ </note>
+
+ <para>
+ The simplified authentication method provided by Seam uses a built-in JAAS login module, <literal>SeamLoginModule</literal>, which
+ delegates authentication to one of your own Seam components. This login module is already configured inside Seam as
+ part of a default application policy and as such does not require any additional configuration files. It allows you to
+ write an authentication method using the entity classes that are provided by your own application, or alternatively to
+ authenticate with some other third party provider. Configuring this simplified form of authentication requires the
+ <literal>identity</literal> component to be configured in <literal>components.xml</literal>:
+ </para>
+
+ <programlisting role="XML"><![CDATA[<components xmlns="http://jboss.com/products/seam/components"
+ xmlns:core="http://jboss.com/products/seam/core"
+ xmlns:security="http://jboss.com/products/seam/security"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation=
+ "http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.2.xsd
+ http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.2.xsd">
+
+ <security:identity authenticate-method="#{authenticator.authenticate}"/>
+
+</components>]]></programlisting>
+
+ <para>
+ The EL expression <literal>#{authenticator.authenticate}</literal> is a method binding that indicates
+ the <literal>authenticate</literal> method of the <literal>authenticator</literal> component will be used
+ to authenticate the user.
+ </para>
+
+ </sect2>
+
+ <sect2>
+ <title>Writing an authentication method</title>
+
+ <para>
+ The <literal>authenticate-method</literal> property specified for <literal>identity</literal> in
+ <literal>components.xml</literal> specifies which method will be used by <literal>SeamLoginModule</literal>
+ to authenticate users. This method takes no parameters, and is expected to return a boolean, which indicates
+ whether authentication is successful or not. The user's username and password can be obtained from
+ <literal>Credentials.getUsername()</literal> and <literal>Credentials.getPassword()</literal>,
+ respectively (you can get a reference to the <literal>credentials</literal> component via
+ <literal>Identity.instance().getCredentials()</literal>). Any roles that the user is a member of
+ should be assigned using <literal>Identity.addRole()</literal>. Here's a complete example of an
+ authentication method inside a POJO component:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Name("authenticator")
+public class Authenticator {
+ @In EntityManager entityManager;
+ @In Credentials credentials;
+ @In Identity identity;
+
+ public boolean authenticate() {
+ try {
+ User user = (User) entityManager.createQuery(
+ "from User where username = :username and password = :password")
+ .setParameter("username", credentials.getUsername())
+ .setParameter("password", credentials.getPassword())
+ .getSingleResult();
+
+ if (user.getRoles() != null) {
+ for (UserRole mr : user.getRoles())
+ identity.addRole(mr.getName());
+ }
+
+ return true;
+ }
+ catch (NoResultException ex) {
+ return false;
+ }
+
+ }
+
+}]]></programlisting>
+
+ <para>
+ In the above example, both <literal>User</literal> and <literal>UserRole</literal> are application-specific
+ entity beans. The <literal>roles</literal> parameter is populated with the roles that the user is a member
+ of, which should be added to the <literal>Set</literal> as literal string values, e.g. "admin", "user".
+ In this case, if the user record is not found and a <literal>NoResultException</literal> thrown, the
+ authentication method returns <literal>false</literal> to indicate the authentication failed.
+ </para>
+
+ <tip>
+ <para>
+ When writing an authenticator method, it is important that it is kept minimal and free from
+ any side-effects. This is because there is no guarantee as to how many times the authenticator
+ method will be called by the security API, and as such it may be invoked multiple times during
+ a single request. Because of this, any special code that should execute upon a successful or
+ failed authentication should be written by implementing an event observer. See the section on
+ Security Events further down in this chapter for more information about which events are
+ raised by Seam Security.
+ </para>
+ </tip>
+
+ <sect3>
+ <title>Identity.addRole()</title>
+
+ <para>
+ The <literal>Identity.addRole()</literal> method behaves differently depending on whether the current
+ session is authenticated or not. If the session is not authenticated, then <literal>addRole()</literal>
+ should <emphasis>only</emphasis> be called during the authentication process. When called here, the
+ role name is placed into a temporary list of pre-authenticated roles. Once authentication is successful,
+ the pre-authenticated roles then become "real" roles, and calling <literal>Identity.hasRole()</literal>
+ for those roles will then return true. The following sequence diagram represents the list of pre-authenticated
+ roles as a first class object to show more clearly how it fits in to the authentication process.
+
+ </para>
+
+ <mediaobject>
+ <imageobject role="fo">
+ <imagedata fileref="images/security-addrole.png" align="center" scalefit="1"/>
+ </imageobject>
+ <imageobject role="html">
+ <imagedata fileref="images/security-addrole.png" align="center"/>
+ </imageobject>
+ </mediaobject>
+
+ <para>
+ If the current session is already authenticated, then calling <literal>Identity.addRole()</literal> will
+ have the expected effect of immediately granting the specified role to the current user.
+ </para>
+
+ </sect3>
+
+ <sect3>
+ <title>Writing an event observer for security-related events</title>
+
+ <para>
+ Say for example, that upon a successful login that some user statistics must be
+ updated. This would be done by writing an event observer for the
+ <literal>org.jboss.seam.security.loginSuccessful</literal> event, like this:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[ @In UserStats userStats;
+
+ @Observer("org.jboss.seam.security.loginSuccessful")
+ public void updateUserStats()
+ {
+ userStats.setLastLoginDate(new Date());
+ userStats.incrementLoginCount();
+ }]]></programlisting>
+
+ <para>
+ This observer method can be placed anywhere, even in the Authenticator component itself.
+ You can find more information about security-related events later in this chapter.
+ </para>
+ </sect3>
+
+ </sect2>
+
+ <sect2>
+ <title>Writing a login form</title>
+
+ <para>
+ The <literal>credentials</literal> component provides both <literal>username</literal> and <literal>password</literal>
+ properties, catering for the most common authentication scenario. These properties can be bound directly to the
+ username and password fields on a login form. Once these properties are set, calling
+ <literal>identity.login()</literal> will authenticate the user using the provided credentials.
+ Here's an example of a simple login form:
+ </para>
+
+ <programlisting role="XHTML"><![CDATA[<div>
+ <h:outputLabel for="name" value="Username"/>
+ <h:inputText id="name" value="#{credentials.username}"/>
+</div>
+
+<div>
+ <h:outputLabel for="password" value="Password"/>
+ <h:inputSecret id="password" value="#{credentials.password}"/>
+</div>
+
+<div>
+ <h:commandButton value="Login" action="#{identity.login}"/>
+</div>]]></programlisting>
+
+ <para>
+ Similarly, logging out the user is done by calling <literal>#{identity.logout}</literal>. Calling this
+ action will clear the security state of the currently authenticated user, and invalidate the user's session.
+ </para>
+
+ </sect2>
+
+ <sect2>
+ <title>Configuration Summary</title>
+ <para>
+ So to sum up, there are the three easy steps to configure authentication:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Configure an authentication method in <literal>components.xml</literal>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Write an authentication method.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Write a login form so that the user can authenticate.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ </sect2>
+
+ <sect2>
+ <title>Remember Me</title>
+
+ <para>
+ Seam Security supports the same kind of "Remember Me" functionality that is commonly encountered in many
+ online web-based applications. It is actually supported in two different "flavours", or modes - the first
+ mode allows the username to be stored in the user's browser as a cookie, and leaves the entering of the
+ password up to the browser (many modern browsers are capable of remembering passwords).
+ </para>
+
+ <para>
+ The second mode supports the storing of a unique token in a cookie, and allows a user to authenticate
+ automatically upon returning to the site, without having to provide a password.
+ </para>
+
+ <warning>
+ <para>
+ Automatic client authentication with a persistent cookie stored on the client machine is dangerous.
+ While convenient for users, any cross-site scripting security hole in your website would have dramatically more
+ serious effects than usual. Without the authentication cookie, the only cookie to steal for an attacker with XSS
+ is the cookie of the current session of a user. This means the attack only works when the user has an open session -
+ which should be a short timespan. However, it is much more attractive and dangerous if an attacker has the possibility
+ to steal a persistent Remember Me cookie that allows him to login without authentication, at any time. Note that this
+ all depends on how well you protect your website against XSS attacks - it's up to you to make sure that your website
+ is 100% XSS safe - a non-trival achievement for any website that allows user input to be rendered on a page.
+ </para>
+
+ <para>
+ Browser vendors recognized this issue and introduced a "Remember Passwords" feature - today almost all browsers support
+ this. Here, the browser remembers the login username and password for a particular website and domain, and fills out the
+ login form automatically when you don't have an active session with the website. If you as a website designer then offer
+ a convenient login keyboard shortcut, this approach is almost as convenient as a "Remember Me" cookie and much safer.
+ Some browsers (e.g. Safari on OS X) even store the login form data in the encrypted global operation system keychain.
+ Or, in a networked environment, the keychain can be transported with the user (between laptop and desktop for example),
+ while browser cookies are usually not synchronized.
+ </para>
+
+ <para>
+ To summarize: While everyone is doing it, persistent "Remember Me" cookies with automatic authentication are a bad
+ practice and should not be used. Cookies that "remember" only the users login name, and fill out the login form with
+ that username as a convenience, are not an issue.
+ </para>
+ </warning>
+
+ <para>
+ To enable the remember me feature for the default (safe, username only) mode, no special configuration is required.
+ In your login form, simply bind the remember me checkbox to <literal>rememberMe.enabled</literal>, like in the following
+ example:
+ </para>
+
+ <programlisting role="XHTML"><![CDATA[ <div>
+ <h:outputLabel for="name" value="User name"/>
+ <h:inputText id="name" value="#{credentials.username}"/>
+ </div>
+
+ <div>
+ <h:outputLabel for="password" value="Password"/>
+ <h:inputSecret id="password" value="#{credentials.password}" redisplay="true"/>
+ </div>
+
+ <div class="loginRow">
+ <h:outputLabel for="rememberMe" value="Remember me"/>
+ <h:selectBooleanCheckbox id="rememberMe" value="#{rememberMe.enabled}"/>
+ </div>]]></programlisting>
+
+ <sect3>
+ <title>Token-based Remember-me Authentication</title>
+
+ <para>
+ To use the automatic, token-based mode of the remember me feature, you must first configure a token store. The
+ most common scenario is to store these authentication tokens within a database (which Seam supports), however it
+ is possible to implement your own token store by implementing the <literal>org.jboss.seam.security.TokenStore</literal>
+ interface. This section will assume you will be using the provided <literal>JpaTokenStore</literal> implementation
+ to store authentication tokens inside a database table.
+ </para>
+
+ <para>
+ The first step is to create a new Entity which will contain the tokens. The following example shows a possible
+ structure that you may use:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Entity
+public class AuthenticationToken implements Serializable {
+ private Integer tokenId;
+ private String username;
+ private String value;
+
+ @Id @GeneratedValue
+ public Integer getTokenId() {
+ return tokenId;
+ }
+
+ public void setTokenId(Integer tokenId) {
+ this.tokenId = tokenId;
+ }
+
+ @TokenUsername
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ @TokenValue
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+}]]></programlisting>
+
+ <para>
+ As you can see from this listing, a couple of special annotations, <literal>@TokenUsername</literal> and
+ <literal>@TokenValue</literal> are used to configure the username and token properties of the entity. These
+ annotations are required for the entity that will contain the authentication tokens.
+ </para>
+
+ <para>
+ The next step is to configure <literal>JpaTokenStore</literal> to use this entity bean to store and retrieve
+ authentication tokens. This is done in <literal>components.xml</literal> by specifying the <literal>token-class</literal>
+ attribute:
+ </para>
+
+ <programlisting role="XML"><![CDATA[
+ <security:jpa-token-store token-class="org.jboss.seam.example.seamspace.AuthenticationToken"/>
+ ]]></programlisting>
+
+ <para>
+ Once this is done, the last thing to do is to configure the <literal>RememberMe</literal> component in
+ <literal>components.xml</literal> also. Its <literal>mode</literal> should be set to <literal>autoLogin</literal>:
+ </para>
+
+ <programlisting role="XML"><![CDATA[ <security:remember-me mode="autoLogin"/>
+ ]]></programlisting>
+
+ <para>
+ That is all that is required - automatic authentication will now occur for users revisiting your site (as long as they
+ check the "remember me" checkbox).
+ </para>
+
+ <para>
+ To ensure that users are automatically authenticated when returning to the site, the following section
+ should be placed in components.xml:
+ </para>
+
+ <programlisting role="XML"><![CDATA[ <event type="org.jboss.seam.security.notLoggedIn">
+ <action execute="#{redirect.captureCurrentView}"/>
+ <action execute="#{identity.tryLogin()}"/>
+ </event>
+ <event type="org.jboss.seam.security.loginSuccessful">
+ <action execute="#{redirect.returnToCapturedView}"/>
+ </event>]]></programlisting>
+
+ </sect3>
+
+ </sect2>
+
+ <sect2>
+ <title>Handling Security Exceptions</title>
+
+ <para>
+ To prevent users from receiving the default error page in response to a security error, it's recommended that
+ <literal>pages.xml</literal> is configured to redirect security errors to a more "pretty" page. The two
+ main types of exceptions thrown by the security API are:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>NotLoggedInException</literal> - This exception is thrown if the user attempts to access a
+ restricted action or page when they are not logged in.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>AuthorizationException</literal> - This exception is only thrown if the user is already logged in,
+ and they have attempted to access a restricted action or page for which they do not have the necessary
+ privileges.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ In the case of a <literal>NotLoggedInException</literal>, it is recommended that the user is redirected to
+ either a login or registration page so that they can log in. For an <literal>AuthorizationException</literal>,
+ it may be useful to redirect the user to an error page. Here's an example of a <literal>pages.xml</literal>
+ file that redirects both of these security exceptions:
+ </para>
+
+ <programlisting role="XML"><![CDATA[<pages>
+
+ ...
+
+ <exception class="org.jboss.seam.security.NotLoggedInException">
+ <redirect view-id="/login.xhtml">
+ <message>You must be logged in to perform this action</message>
+ </redirect>
+ </exception>
+
+ <exception class="org.jboss.seam.security.AuthorizationException">
+ <end-conversation/>
+ <redirect view-id="/security_error.xhtml">
+ <message>You do not have the necessary security privileges to perform this action.</message>
+ </redirect>
+ </exception>
+
+</pages>]]></programlisting>
+
+ <para>
+ Most web applications require even more sophisticated handling of login redirection, so
+ Seam includes some special functionality for handling this problem.
+ </para>
+
+ </sect2>
+
+ <sect2>
+ <title>Login Redirection</title>
+
+ <para>
+ You can ask Seam to redirect the user to a login screen when an unauthenticated user tries
+ to access a particular view (or wildcarded view id) as follows:
+ </para>
+
+ <programlisting role="XML"><![CDATA[<pages login-view-id="/login.xhtml">
+
+ <page view-id="/members/*" login-required="true"/>
+
+ ...
+
+</pages>]]></programlisting>
+
+ <tip>
+ <para>
+ This is less of a blunt instrument than the exception handler shown above, but should
+ probably be used in conjunction with it.
+ </para>
+ </tip>
+
+ <para>
+ After the user logs in, we want to automatically send them back where they came from, so
+ they can retry the action that required logging in. If you add the following event listeners
+ to <literal>components.xml</literal>, attempts to access a restricted view while not logged
+ in will be remembered, so that upon the user successfully logging in they will be redirected
+ to the originally requested view, with any page parameters that existed in the original
+ request.
+ </para>
+
+ <programlisting role="XML"><![CDATA[<event type="org.jboss.seam.security.notLoggedIn">
+ <action execute="#{redirect.captureCurrentView}"/>
+</event>
+
+<event type="org.jboss.seam.security.postAuthenticate">
+ <action execute="#{redirect.returnToCapturedView}"/>
+</event>]]></programlisting>
+
+ <para>
+ Note that login redirection is implemented as a conversation-scoped mechanism, so don't end
+ the conversation in your <literal>authenticate()</literal> method.
+ </para>
+
+ </sect2>
+
+ <sect2>
+ <title>HTTP Authentication</title>
+
+ <para>
+ Although not recommended for use unless absolutely necessary, Seam provides means for authenticating
+ using either HTTP Basic or HTTP Digest (RFC 2617) methods. To use either form of authentication,
+ the <literal>authentication-filter</literal> component must be enabled in components.xml:
+ </para>
+
+ <programlisting role="XML"><![CDATA[
+ <web:authentication-filter url-pattern="*.seam" auth-type="basic"/>
+ ]]></programlisting>
+
+ <para>
+ To enable the filter for basic authentication, set <literal>auth-type</literal> to <literal>basic</literal>,
+ or for digest authentication, set it to <literal>digest</literal>. If using digest authentication, the
+ <literal>key</literal> and <literal>realm</literal> must also be set:
+ </para>
+
+ <programlisting role="XML"><![CDATA[
+ <web:authentication-filter url-pattern="*.seam" auth-type="digest" key="AA3JK34aSDlkj" realm="My App"/>
+ ]]></programlisting>
+
+ <para>
+ The <literal>key</literal> can be any String value. The <literal>realm</literal> is the name of the
+ authentication realm that is presented to the user when they authenticate.
+ </para>
+
+ <sect3>
+ <title>Writing a Digest Authenticator</title>
+
+ <para>
+ If using digest authentication, your authenticator class should extend the abstract class
+ <literal>org.jboss.seam.security.digest.DigestAuthenticator</literal>, and use the
+ <literal>validatePassword()</literal> method to validate the user's plain text password
+ against the digest request. Here is an example:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[
+ public boolean authenticate()
+ {
+ try
+ {
+ User user = (User) entityManager.createQuery(
+ "from User where username = :username")
+ .setParameter("username", identity.getUsername())
+ .getSingleResult();
+
+ return validatePassword(user.getPassword());
+ }
+ catch (NoResultException ex)
+ {
+ return false;
+ }
+ }
+ ]]></programlisting>
+ </sect3>
+
+ </sect2>
+
+ <sect2>
+ <title>Advanced Authentication Features</title>
+
+ <para>
+ This section explores some of the advanced features provided by the security API for addressing more complex
+ security requirements.
+ </para>
+
+ <sect3>
+ <title>Using your container's JAAS configuration</title>
+
+ <para>
+ If you would rather not use the simplified JAAS configuration provided by the Seam Security API, you may
+ instead delegate to the default system JAAS configuration by providing a <literal>jaas-config-name</literal>
+ property in <literal>components.xml</literal>. For example, if you are using JBoss AS and wish to use
+ the <literal>other</literal> policy (which uses the <literal>UsersRolesLoginModule</literal> login module
+ provided by JBoss AS), then the entry in <literal>components.xml</literal> would look like this:
+ </para>
+
+ <programlisting role="XML"><![CDATA[<security:identity jaas-config-name="other"/>]]></programlisting>
+
+ <para>
+ Please keep in mind that doing this does not mean that your user will be authenticated in whichever
+ container your Seam application is deployed in. It merely instructs Seam Security to authenticate
+ itself using the configured JAAS security policy.
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
+
+ <sect1>
+ <title>Identity Management</title>
+
+ <para>
+ Identity Management provides a standard API for the management of a Seam application's users and roles,
+ regardless of which identity store (database, LDAP, etc) is used on the backend. At the center
+ of the Identity Management API is the <literal>identityManager</literal> component, which provides
+ all the methods for creating, modifying and deleting users, granting and revoking roles, changing passwords,
+ enabling and disabling user accounts, authenticating users and listing users and roles.
+ </para>
+
+ <para>
+ Before it may be used, the <literal>identityManager</literal> must first be configured with one or more
+ <literal>IdentityStore</literal>s. These components do the actual work of interacting with the backend
+ security provider, whether it be a database, LDAP server, or something else.
+ </para>
+
+ <mediaobject>
+ <imageobject role="fo">
+ <imagedata fileref="images/security-identitymanager.png" align="center"/>
+ </imageobject>
+ <imageobject role="html">
+ <imagedata fileref="images/security-identitymanager.png" align="center"/>
+ </imageobject>
+ </mediaobject>
+
+ <sect2>
+ <title>Configuring IdentityManager</title>
+
+ <para>
+ The <literal>identityManager</literal> component allows for separate identity stores to be configured
+ for authentication and authorization operations. This means that it is possible for users to
+ be authenticated against one identity store, for example an LDAP directory, yet have their roles
+ loaded from another identity store, such as a relational database.
+ </para>
+
+ <para>
+ Seam provides two <literal>IdentityStore</literal> implementations out of the box;
+ <literal>JpaIdentityStore</literal> uses a relational database to store user and role information,
+ and is the default identity store that is used if nothing is explicitly configured in the
+ <literal>identityManager</literal> component. The other implementation that is provided is
+ <literal>LdapIdentityStore</literal>, which uses an LDAP directory to store users and roles.
+ </para>
+
+ <para>
+ There are two configurable properties for the <literal>identityManager</literal> component -
+ <literal>identityStore</literal> and <literal>roleIdentityStore</literal>. The value for these
+ properties must be an EL expression referring to a Seam component implementing the
+ <literal>IdentityStore</literal> interface. As already mentioned,
+ if left unconfigured then <literal>JpaIdentityStore</literal> will be assumed by default. If
+ only the <literal>identityStore</literal> property is configured, then the same value will be used for
+ <literal>roleIdentityStore</literal> also. For example, the following entry in
+ <literal>components.xml</literal> will configure <literal>identityManager</literal> to use
+ an <literal>LdapIdentityStore</literal> for both user-related and role-related operations:
+ </para>
+
+ <programlisting role="XML"><![CDATA[
+ <security:identity-manager identity-store="#{ldapIdentityStore}"/>
+ ]]></programlisting>
+
+ <para>
+ The following example configures <literal>identityManager</literal> to use an <literal>LdapIdentityStore</literal>
+ for user-related operations, and <literal>JpaIdentityStore</literal> for role-related operations:
+ </para>
+
+ <programlisting role="XML"><![CDATA[
+ <security:identity-manager
+ identity-store="#{ldapIdentityStore}"
+ role-identity-store="#{jpaIdentityStore}"/>
+ ]]></programlisting>
+
+ <para>
+ The following sections explain both of these identity store implementations in greater detail.
+ </para>
+
+ </sect2>
+
+ <sect2>
+ <title>JpaIdentityStore</title>
+
+ <para>
+ This identity store allows for users and roles to be stored inside a relational database. It is designed
+ to be as unrestrictive as possible in regards to database schema design, allowing a great deal of
+ flexibility in the underlying table structure. This is achieved through the use of a set of special
+ annotations, allowing entity beans to be configured to store user and role records.
+ </para>
+
+ <sect3>
+ <title>Configuring JpaIdentityStore</title>
+
+ <para>
+ <literal>JpaIdentityStore</literal> requires that both the <literal>user-class</literal> and
+ <literal>role-class</literal> properties are configured. These properties should refer to the
+ entity classes that are to be used to store both user and role records, respectively. The following
+ example shows the configuration from <literal>components.xml</literal> in the SeamSpace example:
+ </para>
+
+ <programlisting role="XML"><![CDATA[
+ <security:jpa-identity-store
+ user-class="org.jboss.seam.example.seamspace.MemberAccount"
+ role-class="org.jboss.seam.example.seamspace.MemberRole"/>
+ ]]></programlisting>
+
+ </sect3>
+
+ <sect3>
+ <title>Configuring the Entities</title>
+
+ <para>
+ As already mentioned, a set of special annotations are used to configure entity beans for storing
+ users and roles. The following table lists each of the annotations, and their descriptions.
+ </para>
+
+ <table>
+ <title>User Entity Annotations</title>
+
+ <tgroup cols="2">
+ <colspec colnum="1" colwidth="2*" />
+ <colspec colnum="2" colwidth="1*" />
+ <colspec colnum="3" colwidth="3*" />
+
+ <thead>
+ <row>
+ <entry align="center">
+ <para>Annotation</para>
+ </entry>
+ <entry align="center">
+ <para>Status</para>
+ </entry>
+ <entry align="center">
+ <para>Description</para>
+ </entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry>
+ <para>
+ <literal>@UserPrincipal</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>Required</para>
+ </entry>
+ <entry>
+ <para>
+ This annotation marks the field or method containing the user's username.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>@UserPassword</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>Required</para>
+ </entry>
+ <entry>
+ <para>
+ This annotation marks the field or method containing the user's password. It allows a <literal>hash</literal>
+ algorithm to be specified for password hashing. Possible values for <literal>hash</literal> are
+ <literal>md5</literal>, <literal>sha</literal> and <literal>none</literal>. E.g:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@UserPassword(hash = "md5")
+public String getPasswordHash() {
+ return passwordHash;
+}]]></programlisting>
+
+ <para>
+ If an application requires a hash algorithm that isn't supported natively by Seam, it
+ is possible to extend the <literal>PasswordHash</literal> component to implement other
+ hashing algorithms.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>@UserFirstName</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>Optional</para>
+ </entry>
+ <entry>
+ <para>
+ This annotation marks the field or method containing the user's first name.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>@UserLastName</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>Optional</para>
+ </entry>
+ <entry>
+ <para>
+ This annotation marks the field or method containing the user's last name.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>@UserEnabled</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>Optional</para>
+ </entry>
+ <entry>
+ <para>
+ This annotation marks the field or method containing the enabled status of the user. This should be a boolean
+ property, and if not present then all user accounts are assumed to be enabled.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>@UserRoles</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>Required</para>
+ </entry>
+ <entry>
+ <para>
+ This annotation marks the field or method containing the roles of the user. This property will be described in
+ more detail further down.
+ </para>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table>
+ <title>Role Entity Annotations</title>
+
+ <tgroup cols="2">
+ <colspec colnum="1" colwidth="2*" />
+ <colspec colnum="2" colwidth="1*" />
+ <colspec colnum="3" colwidth="3*" />
+
+ <thead>
+ <row>
+ <entry align="center">
+ <para>Annotation</para>
+ </entry>
+ <entry align="center">
+ <para>Status</para>
+ </entry>
+ <entry align="center">
+ <para>Description</para>
+ </entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry>
+ <para>
+ <literal>@RoleName</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>Required</para>
+ </entry>
+ <entry>
+ <para>
+ This annotation marks the field or method containing the name of the role.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>@RoleGroups</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>Optional</para>
+ </entry>
+ <entry>
+ <para>
+ This annotation marks the field or method containing the group memberships of the role.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>@RoleConditional</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>Optional</para>
+ </entry>
+ <entry>
+ <para>
+ This annotation marks the field or method indicating whether the role is conditional or not.
+ Conditional roles are explained later in this chapter.
+ </para>
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect3>
+
+ <sect3>
+ <title>Entity Bean Examples</title>
+
+ <para>
+ As mentioned previously, <literal>JpaIdentityStore</literal> is designed to be as flexible as possible when
+ it comes to the database schema design of your user and role tables. This section looks at a number of
+ possible database schemas that can be used to store user and role records.
+ </para>
+
+ <sect4>
+ <title>Minimal schema example</title>
+
+ <para>
+ In this bare minimal example, a simple user and role table are linked via a
+ many-to-many relationship using a cross-reference table named <literal>UserRoles</literal>.
+ </para>
+
+ <mediaobject>
+ <imageobject role="fo">
+ <imagedata fileref="images/security-entities-1.png" align="center" scalefit="1"/>
+ </imageobject>
+ <imageobject role="html">
+ <imagedata fileref="images/security-entities-1.png" align="center"/>
+ </imageobject>
+ </mediaobject>
+
+ <programlisting role="JAVA"><![CDATA[@Entity
+public class User {
+ private Integer userId;
+ private String username;
+ private String passwordHash;
+ private Set<Role> roles;
+
+ @Id @GeneratedValue
+ public Integer getUserId() { return userId; }
+ public void setUserId(Integer userId) { this.userId = userId; }
+
+ @UserPrincipal
+ public String getUsername() { return username; }
+ public void setUsername(String username) { this.username = username; }
+
+ @UserPassword(hash = "md5")
+ public String getPasswordHash() { return passwordHash; }
+ public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
+
+ @UserRoles
+ @ManyToMany(targetEntity = Role.class)
+ @JoinTable(name = "UserRoles",
+ joinColumns = @JoinColumn(name = "UserId"),
+ inverseJoinColumns = @JoinColumn(name = "RoleId"))
+ public Set<Role> getRoles() { return roles; }
+ public void setRoles(Set<Role> roles) { this.roles = roles; }
+}]]></programlisting>
+<programlisting><![CDATA[@Entity
+public class Role {
+ private Integer roleId;
+ private String rolename;
+
+ @Id @Generated
+ public Integer getRoleId() { return roleId; }
+ public void setRoleId(Integer roleId) { this.roleId = roleId; }
+
+ @RoleName
+ public String getRolename() { return rolename; }
+ public void setRolename(String rolename) { this.rolename = rolename; }
+}]]></programlisting>
+
+ </sect4>
+
+ <sect4>
+ <title>Complex Schema Example</title>
+
+ <para>
+ This example builds on the above minimal example by including all of the optional fields, and allowing
+ group memberships for roles.
+ </para>
+
+ <mediaobject>
+ <imageobject role="fo">
+ <imagedata fileref="images/security-entities-2.png" align="center" scalefit="1"/>
+ </imageobject>
+ <imageobject role="html">
+ <imagedata fileref="images/security-entities-2.png" align="center"/>
+ </imageobject>
+ </mediaobject>
+
+ <programlisting role="JAVA"><![CDATA[@Entity
+public class User {
+ private Integer userId;
+ private String username;
+ private String passwordHash;
+ private Set<Role> roles;
+ private String firstname;
+ private String lastname;
+ private boolean enabled;
+
+ @Id @GeneratedValue
+ public Integer getUserId() { return userId; }
+ public void setUserId(Integer userId) { this.userId = userId; }
+
+ @UserPrincipal
+ public String getUsername() { return username; }
+ public void setUsername(String username) { this.username = username; }
+
+ @UserPassword(hash = "md5")
+ public String getPasswordHash() { return passwordHash; }
+ public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
+
+ @UserFirstName
+ public String getFirstname() { return firstname; }
+ public void setFirstname(String firstname) { this.firstname = firstname; }
+
+ @UserLastName
+ public String getLastname() { return lastname; }
+ public void setLastname(String lastname) { this.lastname = lastname; }
+
+ @UserEnabled
+ public boolean isEnabled() { return enabled; }
+ public void setEnabled(boolean enabled) { this.enabled = enabled; }
+
+ @UserRoles
+ @ManyToMany(targetEntity = Role.class)
+ @JoinTable(name = "UserRoles",
+ joinColumns = @JoinColumn(name = "UserId"),
+ inverseJoinColumns = @JoinColumn(name = "RoleId"))
+ public Set<Role> getRoles() { return roles; }
+ public void setRoles(Set<Role> roles) { this.roles = roles; }
+}]]></programlisting>
+<programlisting><![CDATA[@Entity
+public class Role {
+ private Integer roleId;
+ private String rolename;
+ private boolean conditional;
+
+ @Id @Generated
+ public Integer getRoleId() { return roleId; }
+ public void setRoleId(Integer roleId) { this.roleId = roleId; }
+
+ @RoleName
+ public String getRolename() { return rolename; }
+ public void setRolename(String rolename) { this.rolename = rolename; }
+
+ @RoleConditional
+ public boolean isConditional() { return conditional; }
+ public void setConditional(boolean conditional) { this.conditional = conditional; }
+
+ @RoleGroups
+ @ManyToMany(targetEntity = Role.class)
+ @JoinTable(name = "RoleGroups",
+ joinColumns = @JoinColumn(name = "RoleId"),
+ inverseJoinColumns = @JoinColumn(name = "GroupId"))
+ public Set<Role> getGroups() { return groups; }
+ public void setGroups(Set<Role> groups) { this.groups = groups; }
+
+}]]></programlisting>
+ </sect4>
+
+ </sect3>
+
+ <sect3>
+ <title>JpaIdentityStore Events</title>
+
+ <para>
+ When using <literal>JpaIdentityStore</literal> as the identity store implementation with <literal>IdentityManager</literal>,
+ a few events are raised as a result of invoking certain <literal>IdentityManager</literal> methods.
+ </para>
+
+ <sect4>
+ <title>JpaIdentityStore.EVENT_PRE_PERSIST_USER</title>
+
+ <para>
+ This event is raised in response to calling <literal>IdentityManager.createUser()</literal>. Just before the user
+ entity is persisted to the database, this event will be raised passing the entity instance as an event parameter.
+ The entity will be an instance of the <literal>user-class</literal> configured for <literal>JpaIdentityStore</literal>.
+ </para>
+
+ <para>
+ Writing an observer for this event may be useful for setting additional field values on the entity, which aren't set
+ as part of the standard <literal>createUser()</literal> functionality.
+ </para>
+ </sect4>
+
+ <sect4>
+ <title>JpaIdentityStore.EVENT_USER_CREATED</title>
+
+ <para>
+ This event is also raised in response to calling <literal>IdentityManager.createUser()</literal>. However, it is
+ raised after the user entity has already been persisted to the database. Like the <literal>EVENT_PRE_PERSIST_USER</literal>
+ event, it also passes the entity instance as an event parameter. It may be useful to observe this event if you also
+ need to persist other entities that reference the user entity, for example contact detail records or other user-specific
+ data.
+ </para>
+ </sect4>
+
+ <sect4>
+ <title>JpaIdentityStore.EVENT_USER_AUTHENTICATED</title>
+
+ <para>
+ This event is raised when calling <literal>IdentityManager.authenticate()</literal>. It passes the user entity instance
+ as the event parameter, and is useful for reading additional properties from the user entity that is being authenticated.
+ </para>
+ </sect4>
+ </sect3>
+
+ </sect2>
+
+ <sect2>
+ <title>LdapIdentityStore</title>
+
+ <para>
+ This identity store implementation is designed for working with user records stored in an LDAP directory. It is very
+ highly configurable, allowing great flexibility in how both users and roles are stored in the directory. The following
+ sections describe the configuration options for this identity store, and provide some configuration examples.
+ </para>
+
+ <sect3>
+ <title>Configuring LdapIdentityStore</title>
+
+ <para>
+ The following table describes the available properties that can be configured in <literal>components.xml</literal> for
+ <literal>LdapIdentityStore</literal>.
+ </para>
+
+ <table>
+ <title>LdapIdentityStore Configuration Properties</title>
+
+ <tgroup cols="2">
+ <colspec colnum="1" colwidth="2*" />
+ <colspec colnum="2" colwidth="1*" />
+ <colspec colnum="3" colwidth="3*" />
+
+ <thead>
+ <row>
+ <entry align="center">
+ <para>Property</para>
+ </entry>
+ <entry align="center">
+ <para>Default Value</para>
+ </entry>
+ <entry align="center">
+ <para>Description</para>
+ </entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry>
+ <para>
+ <literal>server-address</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>localhost</literal></para>
+ </entry>
+ <entry>
+ <para>
+ The address of the LDAP server.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>server-port</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>389</literal></para>
+ </entry>
+ <entry>
+ <para>
+ The port number that the LDAP server is listening on.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>user-context-DN</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>ou=Person,dc=acme,dc=com</literal></para>
+ </entry>
+ <entry>
+ <para>
+ The Distinguished Name (DN) of the context containing user records.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>user-DN-prefix</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>uid=</literal></para>
+ </entry>
+ <entry>
+ <para>
+ This value is prefixed to the front of the username to locate the user's record.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>user-DN-suffix</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>,ou=Person,dc=acme,dc=com</literal></para>
+ </entry>
+ <entry>
+ <para>
+ This value is appended to the end of the username to locate the user's record.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>role-context-DN</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>ou=Role,dc=acme,dc=com</literal></para>
+ </entry>
+ <entry>
+ <para>
+ The DN of the context containing role records.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>role-DN-prefix</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>cn=</literal></para>
+ </entry>
+ <entry>
+ <para>
+ This value is prefixed to the front of the role name to form the DN for locating the
+ role record.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>role-DN-suffix</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>,ou=Roles,dc=acme,dc=com</literal></para>
+ </entry>
+ <entry>
+ <para>
+ This value is appended to the role name to form the DN for locating the role record.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>bind-DN</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>cn=Manager,dc=acme,dc=com</literal></para>
+ </entry>
+ <entry>
+ <para>
+ This is the context used to bind to the LDAP server.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>bind-credentials</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>secret</literal></para>
+ </entry>
+ <entry>
+ <para>
+ These are the credentials (the password) used to bind to the LDAP server.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>user-role-attribute</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>roles</literal></para>
+ </entry>
+ <entry>
+ <para>
+ This is the name of the attribute of the user record that contains the list of roles that the
+ user is a member of.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>role-attribute-is-DN</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>true</literal></para>
+ </entry>
+ <entry>
+ <para>
+ This boolean property indicates whether the role attribute of the user record is itself a
+ distinguished name.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>user-name-attribute</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>uid</literal></para>
+ </entry>
+ <entry>
+ <para>
+ Indicates which attribute of the user record contains the username.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>user-password-attribute</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>userPassword</literal></para>
+ </entry>
+ <entry>
+ <para>
+ Indicates which attribute of the user record contains the user's password.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>first-name-attribute</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>null</literal></para>
+ </entry>
+ <entry>
+ <para>
+ Indicates which attribute of the user record contains the user's first name.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>last-name-attribute</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>sn</literal></para>
+ </entry>
+ <entry>
+ <para>
+ Indicates which attribute of the user record contains the user's last name.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>full-name-attribute</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>cn</literal></para>
+ </entry>
+ <entry>
+ <para>
+ Indicates which attribute of the user record contains the user's full (common) name.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>enabled-attribute</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>null</literal></para>
+ </entry>
+ <entry>
+ <para>
+ Indicates which attribute of the user record determines whether the user is enabled.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>role-name-attribute</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>cn</literal></para>
+ </entry>
+ <entry>
+ <para>
+ Indicates which attribute of the role record contains the name of the role.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>object-class-attribute</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>objectClass</literal></para>
+ </entry>
+ <entry>
+ <para>
+ Indicates which attribute determines the class of an object in the directory.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>role-object-classes</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>organizationalRole</literal></para>
+ </entry>
+ <entry>
+ <para>
+ An array of the object classes that new role records should be created as.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>user-object-classes</literal>
+ </para>
+ </entry>
+ <entry>
+ <para><literal>person,uidObject</literal></para>
+ </entry>
+ <entry>
+ <para>
+ An array of the object classes that new user records should be created as.
+ </para>
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect3>
+
+ <sect3>
+ <title>LdapIdentityStore Configuration Example</title>
+
+ <para>
+ The following configuration example shows how <literal>LdapIdentityStore</literal> may be configured for
+ an LDAP directory running on fictional host <literal>directory.mycompany.com</literal>. The users are stored
+ within this directory under the context <literal>ou=Person,dc=mycompany,dc=com</literal>, and are identified
+ using the <literal>uid</literal> attribute (which corresponds to their username). Roles are stored in their
+ own context, <literal>ou=Roles,dc=mycompany,dc=com</literal> and referenced from the user's entry via the
+ <literal>roles</literal> attribute. Role entries are identified by their common name (the <literal>cn</literal> attribute)
+ , which corresponds to the role name. In this example, users may be disabled by setting the value of their
+ <literal>enabled</literal> attribute to false.
+ </para>
+
+ <programlisting role="XML"><![CDATA[
+ <security:ldap-identity-store
+ server-address="directory.mycompany.com"
+ bind-DN="cn=Manager,dc=mycompany,dc=com"
+ bind-credentials="secret"
+ user-DN-prefix="uid="
+ user-DN-suffix=",ou=Person,dc=mycompany,dc=com"
+ role-DN-prefix="cn="
+ role-DN-suffix=",ou=Roles,dc=mycompany,dc=com"
+ user-context-DN="ou=Person,dc=mycompany,dc=com"
+ role-context-DN="ou=Roles,dc=mycompany,dc=com"
+ user-role-attribute="roles"
+ role-name-attribute="cn"
+ user-object-classes="person,uidObject"
+ enabled-attribute="enabled"
+ />]]></programlisting>
+
+ </sect3>
+
+ </sect2>
+
+ <sect2>
+ <title>Writing your own IdentityStore</title>
+
+ <para>
+ Writing your own identity store implementation allows you to authenticate and perform identity management
+ operations against security providers that aren't supported out of the box by Seam. Only a single class is
+ required to achieve this, and it must implement the
+ <literal>org.jboss.seam.security.management.IdentityStore</literal> interface.
+ </para>
+
+ <para>
+ Please refer to the JavaDoc for <literal>IdentityStore</literal> for a description of the methods that
+ must be implemented.
+ </para>
+ </sect2>
+
+
+ <sect2>
+ <title>Authentication with Identity Management</title>
+
+ <para>
+ If you are using the Identity Management features in your Seam application, then it is not required
+ to provide an authenticator component (see previous Authentication section) to enable authentication.
+ Simply omit the <literal>authenticate-method</literal> from the <literal>identity</literal> configuration
+ in <literal>components.xml</literal>, and the <literal>SeamLoginModule</literal> will by default
+ use <literal>IdentityManager</literal> to authenticate your application's users, without any special
+ configuration required.
+ </para>
+ </sect2>
+
+ <sect2>
+ <title>Using IdentityManager</title>
+
+ <para>
+ The <literal>IdentityManager</literal> can be accessed either by injecting it into your Seam
+ component as follows:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[ @In IdentityManager identityManager;]]></programlisting>
+
+ <para>
+ or by accessing it through its static <literal>instance()</literal> method:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[ IdentityManager identityManager = IdentityManager.instance();]]></programlisting>
+
+ <para>
+ The following table describes <literal>IdentityManager</literal>'s API methods:
+ </para>
+
+ <table>
+ <title>Identity Management API</title>
+
+ <tgroup cols="2">
+ <colspec colnum="1" colwidth="1*" />
+ <colspec colnum="2" colwidth="3*" />
+
+ <thead>
+ <row>
+ <entry align="center">
+ <para>Method</para>
+ </entry>
+ <entry align="center">
+ <para>Returns</para>
+ </entry>
+ <entry align="center">
+ <para>Description</para>
+ </entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry>
+ <para>
+ <literal>createUser(String name, String password)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Creates a new user account, with the specified name and password. Returns <literal>true</literal>
+ if successful, or <literal>false</literal> if not.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>deleteUser(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Deletes the user account with the specified name. Returns <literal>true</literal>
+ if successful, or <literal>false</literal> if not.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>createRole(String role)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Creates a new role, with the specified name. Returns <literal>true</literal>
+ if successful, or <literal>false</literal> if not.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>deleteRole(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Deletes the role with the specified name. Returns <literal>true</literal>
+ if successful, or <literal>false</literal> if not.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>enableUser(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Enables the user account with the specified name. Accounts that are not enabled are
+ not able to authenticate. Returns <literal>true</literal> if successful, or
+ <literal>false</literal> if not.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>disableUser(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Disables the user account with the specified name. Returns <literal>true</literal> if
+ successful, or <literal>false</literal> if not.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>changePassword(String name, String password)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Changes the password for the user account with the specified name. Returns
+ <literal>true</literal> if successful, or <literal>false</literal> if not.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>isUserEnabled(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns <literal>true</literal> if the specified user account is enabled, or
+ <literal>false</literal> if it isn't.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>grantRole(String name, String role)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Grants the specified role to the specified user or role. The role must already exist for it to
+ be granted. Returns <literal>true</literal> if the role is successfully granted, or
+ <literal>false</literal> if it is already granted to the user.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>revokeRole(String name, String role)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Revokes the specified role from the specified user or role. Returns <literal>true</literal>
+ if the specified user is a member of the role and it is successfully revoked, or
+ <literal>false</literal> if the user is not a member of the role.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>userExists(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns <literal>true</literal> if the specified user exists, or <literal>false</literal>
+ if it doesn't.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>listUsers()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>List</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns a list of all user names, sorted in alpha-numeric order.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>listUsers(String filter)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>List</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns a list of all user names filtered by the specified filter parameter, sorted in alpha-numeric order.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>listRoles()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>List</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns a list of all role names.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>getGrantedRoles(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>List</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns a list of the names of all the roles explicitly granted to the specified user name.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>getImpliedRoles(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>List</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns a list of the names of all the roles implicitly granted to the specified user name.
+ Implicitly granted roles include those that are not directly granted to a user, rather they are
+ granted to the roles that the user is a member of. For example, is the <literal>admin</literal>
+ role is a member of the <literal>user</literal> role, and a user is a member of the <literal>admin</literal>
+ role, then the implied roles for the user are both the <literal>admin</literal>, and <literal>user</literal>
+ roles.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>authenticate(String name, String password)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Authenticates the specified username and password using the configured Identity Store. Returns
+ <literal>true</literal> if successful or <literal>false</literal> if authentication failed.
+ Successful authentication implies nothing beyond the return value of the method. It does not
+ change the state of the <literal>Identity</literal> component - to perform a proper Seam login the
+ <literal>Identity.login()</literal> must be used instead.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>addRoleToGroup(String role, String group)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Adds the specified role as a member of the specified group. Returns true if the operation is successful.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>removeRoleFromGroup(String role, String group)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Removes the specified role from the specified group. Returns true if the operation is successful.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>listRoles()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>List</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Lists the names of all roles.
+ </para>
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ Using the Identity Management API requires that the calling user has the appropriate authorization to invoke
+ its methods. The following table describes the permission requirements for each of the methods in
+ <literal>IdentityManager</literal>. The permission targets listed below are literal String values.
+ </para>
+
+ <table>
+ <title>Identity Management Security Permissions</title>
+
+ <tgroup cols="2">
+ <colspec colnum="1" colwidth="1*" />
+ <colspec colnum="2" colwidth="3*" />
+
+ <thead>
+ <row>
+ <entry align="center">
+ <para>Method</para>
+ </entry>
+ <entry align="center">
+ <para>Permission Target</para>
+ </entry>
+ <entry align="center">
+ <para>Permission Action</para>
+ </entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry>
+ <para>
+ <literal>createUser()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.user</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>create</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>deleteUser()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.user</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>delete</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>createRole()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.role</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>create</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>deleteRole()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.role</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>delete</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>enableUser()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.user</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>update</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>disableUser()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.user</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>update</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>changePassword()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.user</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>update</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>isUserEnabled()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.user</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>read</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>grantRole()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.user</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>update</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>revokeRole()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.user</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>update</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>userExists()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.user</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>read</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>listUsers()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.user</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>read</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>listRoles()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.role</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>read</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>addRoleToGroup()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.role</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>update</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>removeRoleFromGroup()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.role</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>update</literal>
+ </para>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The following code listing provides an example set of security rules that grants access to all
+ Identity Management-related methods to members of the <literal>admin</literal> role:
+ </para>
+
+ <programlisting><![CDATA[rule ManageUsers
+ no-loop
+ activation-group "permissions"
+when
+ check: PermissionCheck(name == "seam.user", granted == false)
+ Role(name == "admin")
+then
+ check.grant();
+end
+
+rule ManageRoles
+ no-loop
+ activation-group "permissions"
+when
+ check: PermissionCheck(name == "seam.role", granted == false)
+ Role(name == "admin")
+then
+ check.grant();
+end
+]]></programlisting>
+
+ </sect2>
+
+ </sect1>
+
+ <sect1>
+ <title>Error Messages</title>
+
+ <para>
+ The security API produces a number of default faces messages for various security-related events.
+ The following table lists the message keys that can be used to override these messages by specifying
+ them in a <literal>message.properties</literal> resource file. To suppress the message, just put the
+ key with an empty value in the resource file.
+ </para>
+
+ <table>
+ <title>Security Message Keys</title>
+
+ <tgroup cols="2">
+ <colspec colnum="1" colwidth="2*" />
+ <colspec colnum="2" colwidth="3*" />
+
+ <thead>
+ <row>
+ <entry align="center">
+ <para>Message Key</para>
+ </entry>
+ <entry align="center">
+ <para>Description</para>
+ </entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry>
+ <para>
+ <literal>org.jboss.seam.loginSuccessful</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This message is produced when a user successfully logs in via the security API.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>org.jboss.seam.loginFailed</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This message is produced when the login process fails, either because the user provided an
+ incorrect username or password, or because authentication failed in some other way.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>org.jboss.seam.NotLoggedIn</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This message is produced when a user attempts to perform an action or access a page that requires
+ a security check, and the user is not currently authenticated.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>org.jboss.seam.AlreadyLoggedIn</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This message is produced when a user that is already authenticated attempts to log in again.
+ </para>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
+ <sect1>
+ <title>Authorization</title>
+
+ <para>
+ There are a number of authorization mechanisms provided by the Seam Security API for securing access to
+ components, component methods, and pages. This section describes each of these. An important thing to
+ note is that if you wish to use any of the advanced features (such as rule-based permissions) then
+ your <literal>components.xml</literal> may need to be configured to support this - see the Configuration section
+ above.
+ </para>
+
+ <sect2>
+ <title>Core concepts</title>
+
+ <para>
+ Seam Security is built around the premise of users being granted roles and/or permissions, allowing them to
+ perform operations that may not otherwise be permissible for users without the necessary security privileges.
+ Each of the authorization mechanisms provided by the Seam Security API are built upon this core concept of roles and
+ permissions, with an extensible framework providing multiple ways to secure application resources.
+ </para>
+
+ <sect3>
+ <title>What is a role?</title>
+
+ <para>
+ A role is a <emphasis>group</emphasis>, or <emphasis>type</emphasis>, of user that may have been granted certain
+ privileges for performing one or more specific actions within an application. They are simple constructs, consisting
+ of just a name such as "admin", "user", "customer", etc. They can be granted either to users (or in some cases to other
+ roles), and are used to create logical groups of users for the convenient assignment of specific application privileges.
+ </para>
+
+ <mediaobject>
+ <imageobject role="fo">
+ <imagedata fileref="images/security-roleclass.png" align="center"/>
+ </imageobject>
+ <imageobject role="html">
+ <imagedata fileref="images/security-roleclass.png" align="center"/>
+ </imageobject>
+ </mediaobject>
+ </sect3>
+
+ <sect3>
+ <title>What is a permission?</title>
+
+ <para>
+ A permission is a privilege (sometimes once-off) for performing a single, specific action. It is entirely possible to
+ build an application using nothing but permissions, however roles offer a higher level of convenience when granting
+ privileges to groups of users. They are slightly more complex in structure than roles, essentially consisting of three
+ "aspects"; a target, an action, and a recipient. The target of a permission is the object (or an arbitrary name or class)
+ for which a particular action is allowed to be performed by a specific recipient (or user). For example, the user "Bob"
+ may have permission to delete customer objects. In this case, the permission target may be "customer", the permission
+ action would be "delete" and the recipient would be "Bob".
+ </para>
+
+
+ <mediaobject>
+ <imageobject role="fo">
+ <imagedata fileref="images/security-permissionclass.png" align="center"/>
+ </imageobject>
+ <imageobject role="html">
+ <imagedata fileref="images/security-permissionclass.png" align="center"/>
+ </imageobject>
+ </mediaobject>
+
+ <para>
+ Within this documentation, permissions are generally represented in the form <literal>target:action</literal>
+ (omitting the recipient, although in reality one is always required).
+ </para>
+ </sect3>
+
+ </sect2>
+
+ <sect2>
+ <title>Securing components</title>
+
+ <para>
+ Let's start by examining the simplest form of authorization, component security, starting with the
+ <literal>@Restrict</literal> annotation.
+ </para>
+
+ <note>
+ <title>@Restrict vs Typesafe security annotations</title>
+
+ <para>
+ While using the <literal>@Restrict</literal> annotation provides a powerful and flexible method for security component methods
+ due to its ability to support EL expressions, it is recommended that the typesafe equivalent (described later) be
+ used, at least for the compile-time safety it provides.
+ </para>
+ </note>
+
+ <sect3>
+ <title>The @Restrict annotation</title>
+
+ <para>
+ Seam components may be secured either at the method or the class level, using the <literal>@Restrict</literal>
+ annotation. If both a method and it's declaring class are annotated with <literal>@Restrict</literal>,
+ the method restriction will take precedence (and the class restriction will not apply). If a method
+ invocation fails a security check, then an exception will be thrown as per the contract for
+ <literal>Identity.checkRestriction()</literal> (see Inline Restrictions). A <literal>@Restrict</literal>
+ on just the component class itself is equivalent to adding <literal>@Restrict</literal> to each of its
+ methods.
+ </para>
+
+ <para>
+ An empty <literal>@Restrict</literal> implies a permission check of <literal>componentName:methodName</literal>.
+ Take for example the following component method:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Name("account")
+public class AccountAction {
+ @Restrict public void delete() {
+ ...
+ }
+}]]></programlisting>
+
+ <para>
+ In this example, the implied permission required to call the <literal>delete()</literal> method is
+ <literal>account:delete</literal>. The equivalent of this would be to write
+ <literal>@Restrict("#{s:hasPermission('account','delete')}")</literal>. Now let's look at
+ another example:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Restrict @Name("account")
+public class AccountAction {
+ public void insert() {
+ ...
+ }
+ @Restrict("#{s:hasRole('admin')}")
+ public void delete() {
+ ...
+ }
+}]]></programlisting>
+
+ <para>
+ This time, the component class itself is annotated with <literal>@Restrict</literal>. This means that
+ any methods without an overriding <literal>@Restrict</literal> annotation require an implicit permission check.
+ In the case of this example, the <literal>insert()</literal> method requires a permission of
+ <literal>account:insert</literal>, while the <literal>delete()</literal> method requires that the user is a
+ member of the <literal>admin</literal> role.
+ </para>
+
+ <para>
+ Before we go any further, let's address the <literal>#{s:hasRole()}</literal> expression seen in the above
+ example. Both <literal>s:hasRole</literal> and <literal>s:hasPermission</literal> are EL functions, which
+ delegate to the correspondingly named methods of the <literal>Identity</literal> class. These
+ functions can be used within any EL expression throughout the entirety of the security API.
+ </para>
+
+ <para>
+ Being an EL expression, the value of the <literal>@Restrict</literal> annotation may reference any objects that
+ exist within a Seam context. This is extremely useful when performing permission checks for a specific
+ object instance. Look at this example:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Name("account")
+public class AccountAction {
+ @In Account selectedAccount;
+ @Restrict("#{s:hasPermission(selectedAccount,'modify')}")
+ public void modify() {
+ selectedAccount.modify();
+ }
+}]]></programlisting>
+
+ <para>
+ The interesting thing to note from this example is the reference to <literal>selectedAccount</literal>
+ seen within the <literal>hasPermission()</literal> function call. The value of this variable will be
+ looked up from within the Seam context, and passed to the <literal>hasPermission()</literal> method
+ in <literal>Identity</literal>, which in this case can then determine if the user has the required
+ permission for modifying the specified <literal>Account</literal> object.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Inline restrictions</title>
+ <para>
+ Sometimes it might be desirable to perform a security check in code, without using the
+ <literal>@Restrict</literal> annotation. In this situation, simply use
+ <literal>Identity.checkRestriction()</literal> to evaluate a security expression, like this:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[public void deleteCustomer() {
+ Identity.instance().checkRestriction("#{s:hasPermission(selectedCustomer,'delete')}");
+}]]></programlisting>
+
+ <para>
+ If the expression specified doesn't evaluate to <literal>true</literal>, either
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ if the user is not logged in, a <literal>NotLoggedInException</literal>
+ exception is thrown or
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ if the user is logged in, an <literal>AuthorizationException</literal>
+ exception is thrown.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ It is also possible to call the <literal>hasRole()</literal> and <literal>hasPermission()</literal>
+ methods directly from Java code:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[if (!Identity.instance().hasRole("admin"))
+ throw new AuthorizationException("Must be admin to perform this action");
+
+if (!Identity.instance().hasPermission("customer", "create"))
+ throw new AuthorizationException("You may not create new customers");]]></programlisting>
+
+ </sect3>
+ </sect2>
+
+ <sect2>
+ <title>Security in the user interface</title>
+
+ <para>
+ One indication of a well designed user interface is that the user is not presented with options for
+ which they don't have the necessary privileges to use. Seam Security allows conditional rendering of
+ either 1) sections of a page or 2) individual controls, based upon the privileges of the user, using
+ the very same EL expressions that are used for component security.
+ </para>
+
+ <para>
+ Let's take a look at some examples of interface security. First of all, let's pretend that we have a
+ login form that should only be rendered if the user is not already logged in. Using the
+ <literal>identity.isLoggedIn()</literal> property, we can write this:
+ </para>
+
+ <programlisting role="XHTML"><![CDATA[<h:form class="loginForm" rendered="#{not identity.loggedIn}">]]></programlisting>
+
+ <para>
+ If the user isn't logged in, then the login form will be rendered - very straight forward so far.
+ Now let's pretend there is a menu on the page that contains some actions which should only be accessible
+ to users in the <literal>manager</literal> role. Here's one way that these could be written:
+ </para>
+
+ <programlisting role="XHTML"><![CDATA[<h:outputLink action="#{reports.listManagerReports}" rendered="#{s:hasRole('manager')}">
+ Manager Reports
+</h:outputLink>]]></programlisting>
+
+ <para>
+ This is also quite straight forward. If the user is not a member of the <literal>manager</literal>
+ role, then the outputLink will not be rendered. The <literal>rendered</literal> attribute can
+ generally be used on the control itself, or on a surrounding <literal><s:div></literal> or
+ <literal><s:span></literal> control.
+ </para>
+
+ <para>
+ Now for something more complex. Let's say you have a <literal>h:dataTable</literal> control on a
+ page listing records for which you may or may not wish to render action links depending on the
+ user's privileges. The <literal>s:hasPermission</literal> EL function allows us to pass in an
+ object parameter which can be used to determine whether the user has the requested permission
+ for that object or not. Here's how a dataTable with secured links might look:
+ </para>
+
+ <programlisting role="XHTML"><![CDATA[<h:dataTable value="#{clients}" var="cl">
+ <h:column>
+ <f:facet name="header">Name</f:facet>
+ #{cl.name}
+ </h:column>
+ <h:column>
+ <f:facet name="header">City</f:facet>
+ #{cl.city}
+ </h:column>
+ <h:column>
+ <f:facet name="header">Action</f:facet>
+ <s:link value="Modify Client" action="#{clientAction.modify}"
+ rendered="#{s:hasPermission(cl,'modify')}"/>
+ <s:link value="Delete Client" action="#{clientAction.delete}"
+ rendered="#{s:hasPermission(cl,'delete')}"/>
+ </h:column>
+</h:dataTable>]]></programlisting>
+
+ </sect2>
+
+ <sect2>
+ <title>Securing pages</title>
+ <para>
+ Page security requires that the application is using a <literal>pages.xml</literal> file, however is
+ extremely simple to configure. Simply include a <literal><restrict/></literal> element within
+ the <literal>page</literal> elements that you wish to secure. If no explicit restriction is specified
+ by the <literal>restrict</literal> element, an implied permission of <literal>/viewId.xhtml:render</literal>
+ will be checked when the page is accessed via a non-faces (GET) request, and a permission of
+ <literal>/viewId.xhtml:restore</literal> will be required when any JSF postback (form submission) originates
+ from the page. Otherwise, the specified restriction will be evaluated as a standard security expression.
+ Here's a couple of examples:
+ </para>
+
+ <programlisting role="XML"><![CDATA[<page view-id="/settings.xhtml">
+ <restrict/>
+</page>]]></programlisting>
+
+ <para>
+ This page has an implied permission of <literal>/settings.xhtml:render</literal> required for non-faces
+ requests and an implied permission of <literal>/settings.xhtml:restore</literal> for faces requests.
+ </para>
+
+ <programlisting role="XML"><![CDATA[<page view-id="/reports.xhtml">
+ <restrict>#{s:hasRole('admin')}</restrict>
+</page>]]></programlisting>
+
+ <para>
+ Both faces and non-faces requests to this page require that the user is a member of the
+ <literal>admin</literal> role.
+ </para>
+
+ </sect2>
+
+ <sect2>
+ <title>Securing Entities</title>
+
+ <para>
+ Seam security also makes it possible to apply security restrictions to read, insert, update and
+ delete actions for entities.
+ </para>
+
+ <para>
+ To secure all actions for an entity class, add a <literal>@Restrict</literal> annotation on the class
+ itself:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Entity
+@Name("customer")
+@Restrict
+public class Customer {
+ ...
+}]]></programlisting>
+
+ <para>
+ If no expression is specified in the <literal>@Restrict</literal> annotation, the default security check
+ that is performed is a permission check of <literal>entity:action</literal>, where the permission target
+ is the entity instance, and the <literal>action</literal> is either <literal>read</literal>, <literal>insert</literal>,
+ <literal>update</literal> or <literal>delete</literal>.
+ </para>
+
+ <para>
+ It is also possible to only restrict certain actions, by placing a <literal>@Restrict</literal> annotation
+ on the relevent entity lifecycle method (annotated as follows):
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>@PostLoad</literal> - Called after an entity instance is loaded from the database. Use this
+ method to configure a <literal>read</literal> permission.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>@PrePersist</literal> - Called before a new instance of the entity is inserted. Use this method
+ to configure an <literal>insert</literal> permission.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>@PreUpdate</literal> - Called before an entity is updated. Use this method
+ to configure an <literal>update</literal> permission.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>@PreRemove</literal> - Called before an entity is deleted. Use this method
+ to configure a <literal>delete</literal> permission.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ Here's an example of how an entity would be configured to perform a security check for any <literal>insert</literal>
+ operations. Please note that the method is not required to do anything, the only important thing in regard to
+ security is how it is annotated:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[
+ @PrePersist @Restrict
+ public void prePersist() {}
+ ]]></programlisting>
+
+ <note>
+ <title>Using <literal>/META-INF/orm.xml</literal></title>
+
+
+ <para>
+ You can also specify the call back method in <literal>/META-INF/orm.xml</literal>:
+ </para>
+
+ <programlisting role="XML"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
+<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
+ version="1.0">
+
+ <entity class="Customer">
+ <pre-persist method-name="prePersist" />
+ </entity>
+
+</entity-mappings>]]></programlisting>
+
+ <para>
+ Of course, you still need to annotate the <literal>prePersist()</literal>
+ method on <literal>Customer</literal> with <literal>@Restrict</literal>
+ </para>
+ </note>
+
+ <para>
+ And here's an example of an entity permission rule that checks if the authenticated user is allowed to insert
+ a new <literal>MemberBlog</literal> record (from the seamspace example). The entity for which the security
+ check is being made is automatically inserted into the working memory (in this case <literal>MemberBlog</literal>):
+ </para>
+
+ <programlisting><![CDATA[rule InsertMemberBlog
+ no-loop
+ activation-group "permissions"
+when
+ principal: Principal()
+ memberBlog: MemberBlog(member : member -> (member.getUsername().equals(principal.getName())))
+ check: PermissionCheck(target == memberBlog, action == "insert", granted == false)
+then
+ check.grant();
+end;]]></programlisting>
+
+ <para>
+ This rule will grant the permission <literal>memberBlog:insert</literal> if the currently authenticated
+ user (indicated by the <literal>Principal</literal> fact) has the same name as the member for which the
+ blog entry is being created. The "<literal>principal: Principal()</literal>" structure that can be seen in the
+ example code is a variable binding - it binds the instance of the <literal>Principal</literal> object from the
+ working memory (placed there during authentication) and assigns it to a variable called <literal>principal</literal>.
+ Variable bindings allow the value to be referred to in other places, such as the following line which compares the
+ member's username to the <literal>Principal</literal> name. For more details, please refer to the JBoss Rules documentation.
+ </para>
+
+ <para>
+ Finally, we need to install a listener class that integrates Seam security with
+ your JPA provider.
+ </para>
+
+ <sect3>
+ <title>Entity security with JPA</title>
+
+ <para>
+ Security checks for EJB3 entity beans are performed with an <literal>EntityListener</literal>.
+ You can install this listener by using the following <literal>META-INF/orm.xml</literal> file:
+ </para>
+
+ <programlisting role="XML"><![CDATA[<?xml version="1.0" encoding="UTF-8"?>
+<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
+ version="1.0">
+
+ <persistence-unit-metadata>
+ <persistence-unit-defaults>
+ <entity-listeners>
+ <entity-listener class="org.jboss.seam.security.EntitySecurityListener"/>
+ </entity-listeners>
+ </persistence-unit-defaults>
+ </persistence-unit-metadata>
+
+</entity-mappings>]]></programlisting>
+
+ </sect3>
+
+ <sect3>
+ <title>Entity security with a Managed Hibernate Session</title>
+
+ <para>
+ If you are using a Hibernate <literal>SessionFactory</literal> configured via Seam,
+ and are using annotations, or <literal>orm.xml</literal>, then you don't
+ need to do anything special to use entity security.
+ </para>
+
+ </sect3>
+
+ </sect2>
+
+ <sect2>
+ <title>Typesafe Permission Annotations</title>
+
+ <para>
+ Seam provides a number of annotations that may be used as an alternative to <literal>@Restrict</literal>, which have
+ the added advantage of providing compile-time safety as they don't support arbitrary EL expressions in the same way
+ that <literal>@Restrict</literal> does.
+ </para>
+
+ <para>
+ Out of the box, Seam comes with annotations for standard CRUD-based permissions, however it is a simple matter to
+ add your own. The following annotations are provided in the <literal>org.jboss.seam.annotations.security</literal> package:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>@Insert</para>
+ </listitem>
+ <listitem>
+ <para>@Read</para>
+ </listitem>
+ <listitem>
+ <para>@Update</para>
+ </listitem>
+ <listitem>
+ <para>@Delete</para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ To use these annotations, simply place them on the method or parameter for which you wish to perform a security check.
+ If placed on a method, then they should specify a target class for which the permission will be checked. Take the
+ following example:
+ </para>
+
+ <programlisting><![CDATA[ @Insert(Customer.class)
+ public void createCustomer() {
+ ...
+ }]]></programlisting>
+
+ <para>
+ In this example, a permission check will be performed for the user to ensure that they have the rights to create
+ new <literal>Customer</literal> objects. The target of the permission check will be <literal>Customer.class</literal>
+ (the actual <literal>java.lang.Class</literal> instance itself), and the action is the lower case representation of the
+ annotation name, which in this example is <literal>insert</literal>.
+ </para>
+
+ <para>
+ It is also possible to annotate the parameters of a component method in the same way. If this is done, then it is
+ not required to specify a permission target (as the parameter value itself will be the target of the permission check):
+ </para>
+
+ <programlisting><![CDATA[ public void updateCustomer(@Update Customer customer) {
+ ...
+ }]]></programlisting>
+
+ <para>
+ To create your own security annotation, you simply need to annotate it with <literal>@PermissionCheck</literal>, for example:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Target({METHOD, PARAMETER})
+@Documented
+@Retention(RUNTIME)
+@Inherited
+@PermissionCheck
+public @interface Promote {
+ Class value() default void.class;
+}]]></programlisting>
+
+ <para>
+ If you wish to override the default permisison action name (which is the lower case version of the annotation name) with
+ another value, you can specify it within the <literal>@PermissionCheck</literal> annotation:
+ </para>
+
+ <programlisting><![CDATA[@PermissionCheck("upgrade")]]></programlisting>
+
+ </sect2>
+
+ <sect2>
+ <title>Typesafe Role Annotations</title>
+
+ <para>
+ In addition to supporting typesafe permission annotation, Seam Security also provides typesafe role annotations that
+ allow you to restrict access to component methods based on the role memberships of the currently authenticated user.
+ Seam provides one such annotation out of the box, <literal>org.jboss.seam.annotations.security.Admin</literal>, used
+ to restrict access to a method to users that are a member of the <literal>admin</literal> role (so long as your
+ own application supports such a role). To create your own role annotations, simply meta-annotate them with
+ <literal>org.jboss.seam.annotations.security.RoleCheck</literal>, like in the following example:
+ </para>
+
+ <programlisting><![CDATA[@Target({METHOD})
+@Documented
+@Retention(RUNTIME)
+@Inherited
+@RoleCheck
+public @interface User {
+}]]></programlisting>
+
+ <para>
+ Any methods subsequently annotated with the <literal>@User</literal> annotation as shown in the above example
+ will be automatically intercepted and the user checked for the membership of the corresponding role name
+ (which is the lower case version of the annotation name, in this case <literal>user</literal>).
+ </para>
+
+ </sect2>
+
+ <sect2>
+ <title>The Permission Authorization Model</title>
+
+ <para>
+ Seam Security provides an extensible framework for resolving application permissions. The following class diagram
+ shows an overview of the main components of the permission framework:
+ </para>
+
+ <mediaobject>
+ <imageobject role="fo">
+ <imagedata fileref="images/security-permission-classdiagram.png" align="center"/>
+ </imageobject>
+ <imageobject role="html">
+ <imagedata fileref="images/security-permission-classdiagram.png" align="center"/>
+ </imageobject>
+ </mediaobject>
+
+ <para>
+ The relevant classes are explained in more detail in the following sections.
+ </para>
+
+ <sect3>
+ <title>PermissionResolver</title>
+
+ <para>
+ This is actually an interface, which provides methods for resolving individual object permissions. Seam provides
+ the following built-in <literal>PermissionResolver</literal> implementations, which are described in more detail later
+ in the chapter:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para><literal>RuleBasedPermissionResolver</literal> - This permission resolver uses Drools to resolve rule-based
+ permission checks.</para>
+ </listitem>
+ <listitem>
+ <para><literal>PersistentPermissionResolver</literal> - This permission resolver stores object permissions in a
+ persistent store, such as a relational database.</para>
+ </listitem>
+ </itemizedlist>
+
+ <sect4>
+ <title>Writing your own PermissionResolver</title>
+
+ <para>
+ It is very simple to implement your own permission resolver. The <literal>PermissionResolver</literal>
+ interface defines only two methods that must be implemented, as shown by the following table. By deploying
+ your own <literal>PermissionResolver</literal> implementation in your Seam project, it will be automatically
+ scanned during deployment and registered with the default <literal>ResolverChain</literal>.
+ </para>
+
+ <table>
+ <title>PermissionResolver interface</title>
+
+ <tgroup cols="2">
+ <colspec colnum="1" colwidth="2*" />
+ <colspec colnum="2" colwidth="3*" />
+ <colspec colnum="3" colwidth="3*" />
+ <colspec colnum="4" colwidth="4*" />
+
+ <thead>
+ <row>
+ <entry align="center">
+ <para>Return type</para>
+ </entry>
+ <entry align="center">
+ <para>Method</para>
+ </entry>
+ <entry align="center">
+ <para>Description</para>
+ </entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>hasPermission(Object target, String action)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This method must resolve whether the currently authenticated user (obtained via a call to
+ <literal>Identity.getPrincipal()</literal>) has the permission specified by the <literal>target</literal>
+ and <literal>action</literal> parameters. It should return <literal>true</literal> if the user has
+ the permission, or <literal>false</literal> if they don't.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>void</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>filterSetByAction(Set<Object> targets, String action)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This method should remove any objects from the specified set, that would
+ return <literal>true</literal> if passed to the <literal>hasPermission()</literal> method with the
+ same <literal>action</literal> parameter value.
+ </para>
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ <note>
+ <para>
+ As they are cached in the user's session, any custom <literal>PermissionResolver</literal>
+ implementations must adhere to a couple of restrictions. Firstly, they may not contain any
+ state that is finer-grained than session scope (and the scope of the component itself should
+ either be application or session). Secondly, they must not use dependency
+ injection as they may be accessed from multiple threads simultaneously. In fact, for
+ performance reasons it is recommended that they are annotated with
+ <literal>@BypassInterceptors</literal> to bypass Seam's interceptor stack altogether.
+ </para>
+ </note>
+
+ </sect4>
+ </sect3>
+
+ <sect3>
+ <title>ResolverChain</title>
+
+ <para>
+ A <literal>ResolverChain</literal> contains an ordered list of <literal>PermissionResolver</literal>s, for the
+ purpose of resolving object permissions for a particular object class or permission target.
+ </para>
+
+ <para>
+ The default <literal>ResolverChain</literal> consists of all permission resolvers discovered during
+ application deployment. The <literal>org.jboss.seam.security.defaultResolverChainCreated</literal>
+ event is raised (and the <literal>ResolverChain</literal> instance passed as an event parameter)
+ when the default <literal>ResolverChain</literal> is created. This allows additional resolvers that
+ for some reason were not discovered during deployment to be added, or for resolvers that are in the
+ chain to be re-ordered or removed.
+ </para>
+
+ <para>
+ The following sequence diagram shows the interaction between the components of the permission framework during a
+ permission check (explanation follows). A permission check can originate from a number of possible sources,
+ for example - the security interceptor, the <literal>s:hasPermission</literal> EL function, or via an API
+ call to <literal>Identity.checkPermission</literal>:
+ </para>
+
+ <mediaobject>
+ <imageobject role="fo">
+ <imagedata fileref="images/security-permission-sequence.png" align="center"/>
+ </imageobject>
+ <imageobject role="html">
+ <imagedata fileref="images/security-permission-sequence.png" align="center"/>
+ </imageobject>
+ </mediaobject>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ 1. A permission check is initiated somewhere (either in code or via an EL
+ expression) resulting in a call to <literal>Identity.hasPermission()</literal>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ 1.1. <literal>Identity</literal> invokes
+ <literal>PermissionMapper.resolvePermission()</literal>, passing in the
+ permission to be resolved.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ 1.1.1. <literal>PermissionMapper</literal> maintains a <literal>Map</literal> of
+ <literal>ResolverChain</literal> instances, keyed by class. It uses this map
+ to locate the correct <literal>ResolverChain</literal> for the permission's
+ target object. Once it has the correct <literal>ResolverChain</literal>, it
+ retrieves the list of <literal>PermissionResolver</literal>s it contains via
+ a call to <literal>ResolverChain.getResolvers()</literal>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ 1.1.2. For each <literal>PermissionResolver</literal> in the <literal>ResolverChain</literal>,
+ the <literal>PermissionMapper</literal> invokes its <literal>hasPermission()</literal> method,
+ passing in the permission instance to be checked. If any of the <literal>PermissionResolver</literal>s
+ return <literal>true</literal>, then the permission check has succeeded and the
+ <literal>PermissionMapper</literal> also returns <literal>true</literal> to <literal>Identity</literal>.
+ If none of the <literal>PermissionResolver</literal>s return true, then the permission check
+ has failed.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </sect3>
+
+ </sect2>
+
+ <sect2>
+ <title>RuleBasedPermissionResolver</title>
+
+ <para>
+ One of the built-in permission resolvers provided by Seam, <literal>RuleBasedPermissionResolver</literal>
+ allows permissions to be evaluated based on a set of Drools (JBoss Rules) security rules. A couple of the
+ advantages of using a rule engine are 1) a centralized location for the business logic that is used to
+ evaluate user permissions, and 2) speed - Drools uses very efficient algorithms for evaluating large
+ numbers of complex rules involving multiple conditions.
+ </para>
+
+ <sect3>
+ <title>Requirements</title>
+
+ <para>
+ If using the rule-based permission features provided by Seam Security, the following jar files are required by Drools
+ to be distributed with your project:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>drools-api.jar</para>
+ </listitem>
+ <listitem>
+ <para>drools-compiler.jar</para>
+ </listitem>
+ <listitem>
+ <para>drools-core.jar</para>
+ </listitem>
+ <listitem>
+ <para>drools-decisiontables.jar</para>
+ </listitem>
+ <listitem>
+ <para>drools-templates.jar</para>
+ </listitem>
+ <listitem>
+ <para>janino.jar</para>
+ </listitem>
+ <listitem>
+ <para>antlr-runtime.jar</para>
+ </listitem>
+ <listitem>
+ <para>mvel2.jar</para>
+ </listitem>
+ </itemizedlist>
+
+ </sect3>
+
+ <sect3>
+ <title>Configuration</title>
+
+ <para>
+ The configuration for <literal>RuleBasedPermissionResolver</literal> requires that a Drools rule base is first
+ configured in <literal>components.xml</literal>. By default, it expects that the rule base is named
+ <literal>securityRules</literal>, as per the following example:
+ </para>
+
+ <programlisting role="XML"><![CDATA[<components xmlns="http://jboss.com/products/seam/components"
+ xmlns:core="http://jboss.com/products/seam/core"
+ xmlns:security="http://jboss.com/products/seam/security"
+ xmlns:drools="http://jboss.com/products/seam/drools"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation=
+ "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.2.xsd
+ http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.2.xsd
+ http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-2.2.xsd
+ http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.2.xsd">
+
+ <drools:rule-base name="securityRules">
+ <drools:rule-files>
+ <value>/META-INF/security.drl</value>
+ </drools:rule-files>
+ </drools:rule-base>
+
+ </components>]]></programlisting>
+
+ <para>
+ The default rule base name can be overridden by specifying the <literal>security-rules</literal>
+ property for <literal>RuleBasedPermissionResolver</literal>:
+ </para>
+
+ <programlisting><![CDATA[
+ <security:rule-based-permission-resolver security-rules="#{prodSecurityRules}"/>]]></programlisting>
+
+ <para>
+ Once the <literal>RuleBase</literal> component is configured, it's time to write the security rules.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Writing Security Rules</title>
+
+ <para>
+ The first step to writing security rules is to create a new rule file in the <literal>/META-INF</literal>
+ directory of your application's jar file. Usually this file would be named something like
+ <literal>security.drl</literal>, however you can name it whatever you like as long as it is configured
+ correspondingly in <literal>components.xml</literal>.
+ </para>
+
+ <para>
+ So what should the security rules file contain? At this stage it might be a good idea to at least skim
+ through the Drools documentation, however to get started here's an extremely simple example:
+ </para>
+
+ <programlisting><![CDATA[package MyApplicationPermissions;
+
+ import org.jboss.seam.security.permission.PermissionCheck;
+ import org.jboss.seam.security.Role;
+
+ rule CanUserDeleteCustomers
+ when
+ c: PermissionCheck(target == "customer", action == "delete")
+ Role(name == "admin")
+ then
+ c.grant();
+ end]]></programlisting>
+
+ <para>
+ Let's break this down step by step. The first thing we see is the package declaration. A package in Drools
+ is essentially a collection of rules. The package name can be anything you want - it doesn't relate
+ to anything else outside the scope of the rule base.
+ </para>
+
+ <para>
+ The next thing we can notice is a couple of import statements for the <literal>PermissionCheck</literal>
+ and <literal>Role</literal> classes. These imports inform the rules engine that we'll be referencing
+ these classes within our rules.
+ </para>
+
+ <para>
+ Finally we have the code for the rule. Each rule within a package should be given a unique name (usually
+ describing the purpose of the rule). In this case our rule is called <literal>CanUserDeleteCustomers</literal>
+ and will be used to check whether a user is allowed to delete a customer record.
+ </para>
+
+ <para>
+ Looking at the body of the rule definition we can notice two distinct sections. Rules have what is known
+ as a left hand side (LHS) and a right hand side (RHS). The LHS consists of the conditional part of the
+ rule, i.e. a list of conditions which must be satisfied for the rule to fire. The LHS is represented by
+ the <literal>when</literal> section. The RHS is the consequence, or action section of the rule that will
+ only be fired if all of the conditions in the LHS are met. The RHS is represented by the
+ <literal>then</literal> section. The end of the rule is denoted by the <literal>end</literal> line.
+ </para>
+
+ <para>
+ If we look at the LHS of the rule, we see two conditions listed there. Let's examine the first condition:
+ </para>
+
+ <programlisting><![CDATA[c: PermissionCheck(target == "customer", action == "delete")]]></programlisting>
+
+ <para>
+ In plain english, this condition is stating that there must exist a <literal>PermissionCheck</literal> object
+ with a <literal>target</literal> property equal to "customer", and an <literal>action</literal> property equal
+ to "delete" within the working memory.
+ </para>
+
+ <para>
+ So what is the working memory? Also known as a "stateful session" in Drools terminology, the working memory
+ is a session-scoped object that contains the contextual information that is required by the rules engine to
+ make a decision about a permission check. Each time the <literal>hasPermission()</literal> method is called,
+ a temporary <literal>PermissionCheck</literal> object, or <emphasis>Fact</emphasis>, is inserted into the
+ working memory. This <literal>PermissionCheck</literal> corresponds exactly to the permission that is being
+ checked, so for example if you call <literal>hasPermission("account", "create")</literal> then a
+ <literal>PermissionCheck</literal> object with a <literal>target</literal> equal to "account" and
+ <literal>action</literal> equal to "create" will be inserted into the working memory for the duration of the
+ permission check.
+ </para>
+
+ <para>
+ Besides the <literal>PermissionCheck</literal> facts, there is also a <literal>org.jboss.seam.security.Role</literal>
+ fact for each of the roles that the authenticated user is a member of. These <literal>Role</literal> facts
+ are synchronized with the user's authenticated roles at the beginning of every permission check. As a consequence,
+ any <literal>Role</literal> object that is inserted into the working memory during the course of a permission
+ check will be removed before the next permission check occurs, if the authenticated user is not actually a member of
+ that role. Besides the <literal>PermissionCheck</literal> and <literal>Role</literal> facts, the working
+ memory also contains the <literal>java.security.Principal</literal> object that was created as a result of
+ the authentication process.
+ </para>
+
+ <para>
+ It is also possible to insert additional long-lived facts into the working memory by calling
+ <literal>RuleBasedPermissionResolver.instance().getSecurityContext().insert()</literal>,
+ passing the object as a parameter. The exception to this is <literal>Role</literal> objects, which as
+ already discussed are synchronized at the start of each permission check.
+ </para>
+
+ <para>
+ Getting back to our simple example, we can also notice that the first line of our LHS is prefixed with
+ <literal>c:</literal>. This is a variable binding, and is used to refer back to the object that is
+ matched by the condition (in this case, the <literal>PermissionCheck</literal>). Moving on to the
+ second line of our LHS, we see this:
+ </para>
+
+ <programlisting><![CDATA[Role(name == "admin")]]></programlisting>
+
+ <para>
+ This condition simply states that there must be a <literal>Role</literal> object with a
+ <literal>name</literal> of "admin" within the working memory. As already mentioned, user roles are inserted into
+ the working memory at the beginning of each permission check. So, putting both conditions together, this
+ rule is essentially saying "I will fire if you are checking for the <literal>customer:delete</literal>
+ permission and the user is a member of the <literal>admin</literal> role".
+ </para>
+
+ <para>
+ So what is the consequence of the rule firing? Let's take a look at the RHS of the rule:
+ </para>
+
+ <programlisting><![CDATA[c.grant()]]></programlisting>
+
+ <para>
+ The RHS consists of Java code, and in this case is invoking the <literal>grant()</literal>
+ method of the <literal>c</literal> object, which as already mentioned is a variable binding
+ for the <literal>PermissionCheck</literal> object. Besides the <literal>name</literal> and
+ <literal>action</literal> properties of the <literal>PermissionCheck</literal> object, there
+ is also a <literal>granted</literal> property which is initially set to <literal>false</literal>.
+ Calling <literal>grant()</literal> on a <literal>PermissionCheck</literal> sets the
+ <literal>granted</literal> property to <literal>true</literal>, which means that the permission
+ check was successful, allowing the user to carry out whatever action the permission check was
+ intended for.
+ </para>
+ </sect3>
+
+ <sect3>
+ <title>Non-String permission targets</title>
+
+ <para>
+ So far we have only seen permission checks for String-literal permission targets. It is of course also
+ possible to write security rules for permission targets of more complex types. For example, let's say that you wish
+ to write a security rule to allow your users to create blog comments. The following rule demonstrates
+ how this may be expressed, by requiring the target of the permission check to be an instance of
+ <literal>MemberBlog</literal>, and also requiring that the currently authenticated user is a member of the
+ <literal>user</literal> role:
+ </para>
+
+ <programlisting><![CDATA[rule CanCreateBlogComment
+ no-loop
+ activation-group "permissions"
+when
+ blog: MemberBlog()
+ check: PermissionCheck(target == blog, action == "create", granted == false)
+ Role(name == "user")
+then
+ check.grant();
+end
+]]></programlisting>
+
+ </sect3>
+
+ <sect3>
+ <title>Wildcard permission checks</title>
+
+ <para>
+ It is possible to implement a wildcard permission check (which allows all actions for a given permission
+ target), by omitting the <literal>action</literal> constraint for the <literal>PermissionCheck</literal> in
+ your rule, like this:
+ </para>
+
+ <programlisting><![CDATA[rule CanDoAnythingToCustomersIfYouAreAnAdmin
+when
+ c: PermissionCheck(target == "customer")
+ Role(name == "admin")
+then
+ c.grant();
+end;
+ ]]></programlisting>
+
+ <para>
+ This rule allows users with the <literal>admin</literal> role to perform <emphasis>any</emphasis> action for
+ any <literal>customer</literal> permission check.
+ </para>
+
+ </sect3>
+
+ </sect2>
+
+ <sect2>
+ <title>PersistentPermissionResolver</title>
+
+ <para>
+ Another built-in permission resolver provided by Seam, <literal>PersistentPermissionResolver</literal>
+ allows permissions to be loaded from persistent storage, such as a relational database. This permission
+ resolver provides ACL style instance-based security, allowing for specific object permissions to be assigned
+ to individual users and roles. It also allows for persistent, arbitrarily-named permission targets (not
+ necessarily object/class based) to be assigned in the same way.
+ </para>
+
+ <sect3>
+ <title>Configuration</title>
+
+ <para>
+ Before it can be used, <literal>PersistentPermissionResolver</literal> must be configured with a
+ valid <literal>PermissionStore</literal> in <literal>components.xml</literal>. If not configured,
+ it will attempt to use the default permission store, <literal>JpaIdentityStore</literal> (see section
+ further down for details). To use a permission store other than the default, configure the
+ <literal>permission-store</literal> property as follows:
+ </para>
+
+ <programlisting><![CDATA[ <security:persistent-permission-resolver permission-store="#{myCustomPermissionStore}"/>]]></programlisting>
+
+ </sect3>
+
+ <sect3>
+ <title>Permission Stores</title>
+
+ <para>
+ A permission store is required for <literal>PersistentPermissionResolver</literal> to connect to the
+ backend storage where permissions are persisted. Seam provides one <literal>PermissionStore</literal>
+ implementation out of the box, <literal>JpaPermissionStore</literal>, which is used to store
+ permissions inside a relational database. It is possible to write your own permission store
+ by implementing the <literal>PermissionStore</literal> interface, which defines the following
+ methods:
+ </para>
+
+ <table>
+ <title>PermissionStore interface</title>
+
+ <tgroup cols="2">
+ <colspec colnum="1" colwidth="2*" />
+ <colspec colnum="2" colwidth="3*" />
+ <colspec colnum="3" colwidth="3*" />
+
+ <thead>
+ <row>
+ <entry align="center">
+ <para>Return type</para>
+ </entry>
+ <entry align="center">
+ <para>Method</para>
+ </entry>
+ <entry align="center">
+ <para>Description</para>
+ </entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry>
+ <para>
+ <literal>List<Permission></literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>listPermissions(Object target)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This method should return a <literal>List</literal> of <literal>Permission</literal> objects
+ representing all the permissions granted for the specified target object.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>List<Permission></literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>listPermissions(Object target, String action)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This method should return a <literal>List</literal> of <literal>Permission</literal> objects
+ representing all the permissions with the specified action, granted for the specified target object.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>List<Permission></literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>listPermissions(Set<Object> targets, String action)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This method should return a <literal>List</literal> of <literal>Permission</literal> objects
+ representing all the permissions with the specified action, granted for the specified set of
+ target objects.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>grantPermission(Permission)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This method should persist the specified <literal>Permission</literal> object to the backend
+ storage, returning true if successful.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>grantPermissions(List<Permission> permissions)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This method should persist all of the <literal>Permission</literal> objects contained in the
+ specified <literal>List</literal>, returning true if successful.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>revokePermission(Permission permission)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This method should remove the specified <literal>Permission</literal> object from persistent storage.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>revokePermissions(List<Permission> permissions)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This method should remove all of the <literal>Permission</literal> objects in the specified list
+ from persistent storage.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>List<String></literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>listAvailableActions(Object target)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This method should return a list of all the available actions (as Strings) for the class of the
+ specified target object. It is used in conjunction with permission management to build the user
+ interface for granting specific class permissions (see section further down).
+ </para>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect3>
+
+ <sect3>
+ <title>JpaPermissionStore</title>
+
+ <para>
+ This is the default <literal>PermissionStore</literal> implementation (and the only one provided by Seam), which
+ uses a relational database to store permissions. Before it can be used it must be configured with either one or
+ two entity classes for storing user and role permissions. These entity classes must be annotated with a special
+ set of security annotations to configure which properties of the entity correspond to various aspects of the
+ permissions being stored.
+ </para>
+
+ <para>
+ If you wish to use the same entity (i.e. a single database table) to store both user and role permissions, then
+ only the <literal>user-permission-class</literal> property is required to be configured. If you wish to use
+ separate tables for storing user and role permissions, then in addition to the <literal>user-permission-class</literal>
+ property you must also configure the <literal>role-permission-class</literal> property.
+ </para>
+
+ <para>For example, to configure a single entity class to store both user and role permissions:</para>
+
+ <programlisting role="XML"><![CDATA[<security:jpa-permission-store user-permission-class="com.acme.model.AccountPermission"/>]]></programlisting>
+
+ <para>To configure separate entity classes for storing user and role permissions:</para>
+
+ <programlisting role="XML"><![CDATA[<security:jpa-permission-store user-permission-class="com.acme.model.UserPermission"
+ role-permission-class="com.acme.model.RolePermission"/>]]></programlisting>
+
+ <sect4>
+ <title>Permission annotations</title>
+
+ <para>
+ As mentioned, the entity classes that contain the user and role permissions must be configured with a
+ special set of annotations, contained within the <literal>org.jboss.seam.annotations.security.permission</literal> package.
+ The following table lists each of these annotations along with a description of how they are used:
+ </para>
+
+ <table>
+ <title>Entity Permission annotations</title>
+
+ <tgroup cols="2">
+ <colspec colnum="1" colwidth="2*" />
+ <colspec colnum="2" colwidth="3*" />
+ <colspec colnum="3" colwidth="3*" />
+
+ <thead>
+ <row>
+ <entry align="center">
+ <para>Annotation</para>
+ </entry>
+ <entry align="center">
+ <para>Target</para>
+ </entry>
+ <entry align="center">
+ <para>Description</para>
+ </entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry>
+ <para>
+ <literal>@PermissionTarget</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>FIELD,METHOD</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This annotation identifies the property of the entity that will contain the permission target. The property
+ should be of type <literal>java.lang.String</literal>.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>@PermissionAction</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>FIELD,METHOD</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This annotation identifies the property of the entity that will contain the permission action. The property
+ should be of type <literal>java.lang.String</literal>.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>@PermissionUser</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>FIELD,METHOD</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This annotation identifies the property of the entity that will contain the recipient user for the permission. It should
+ be of type <literal>java.lang.String</literal> and contain the user's username.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>@PermissionRole</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>FIELD,METHOD</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This annotation identifies the property of the entity that will contain the recipient role for the permission. It should
+ be of type <literal>java.lang.String</literal> and contain the role name.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>@PermissionDiscriminator</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>FIELD,METHOD</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This annotation should be used when the same entity/table is used to store both user and role permissions. It identifies
+ the property of the entity that is used to discriminate between user and role permissions. By default, if the column
+ value contains the string literal <literal>user</literal>, then the record will be treated as a user permission. If it
+ contains the string literal <literal>role</literal>, then it will be treated as a role permission. It is also possible
+ to override these defaults by specifying the <literal>userValue</literal> and <literal>roleValue</literal> properties
+ within the annotation. For example, to use <literal>u</literal> and <literal>r</literal> instead of <literal>user</literal>
+ and <literal>role</literal>, the annotation would be written like this:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@PermissionDiscriminator(userValue = "u", roleValue = "r")]]></programlisting>
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect4>
+
+ <sect4>
+ <title>Example Entity</title>
+
+ <para>
+ Here is an example of an entity class that is used to store both user and role permissions. The following class can be found
+ inside the SeamSpace example:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[
+@Entity
+public class AccountPermission implements Serializable {
+ private Integer permissionId;
+ private String recipient;
+ private String target;
+ private String action;
+ private String discriminator;
+
+ @Id @GeneratedValue
+ public Integer getPermissionId() {
+ return permissionId;
+ }
+
+ public void setPermissionId(Integer permissionId) {
+ this.permissionId = permissionId;
+ }
+
+ @PermissionUser @PermissionRole
+ public String getRecipient() {
+ return recipient;
+ }
+
+ public void setRecipient(String recipient) {
+ this.recipient = recipient;
+ }
+
+ @PermissionTarget
+ public String getTarget() {
+ return target;
+ }
+
+ public void setTarget(String target) {
+ this.target = target;
+ }
+
+ @PermissionAction
+ public String getAction() {
+ return action;
+ }
+
+ public void setAction(String action) {
+ this.action = action;
+ }
+
+ @PermissionDiscriminator
+ public String getDiscriminator() {
+ return discriminator;
+ }
+
+ public void setDiscriminator(String discriminator) {
+ this.discriminator = discriminator;
+ }
+}
+ ]]></programlisting>
+
+ <para>
+ As can be seen in the above example, the <literal>getDiscriminator()</literal> method has been annotated
+ with the <literal>@PermissionDiscriminator</literal> annotation, to allow <literal>JpaPermissionStore</literal> to
+ determine which records represent user permissions and which represent role permissions. In addition, it
+ can also be seen that the <literal>getRecipient()</literal> method is annotated with both
+ <literal>@PermissionUser</literal> and <literal>@PermissionRole</literal> annotations. This is perfectly valid,
+ and simply means that the <literal>recipient</literal> property of the entity will either contain the name
+ of the user or the name of the role, depending on the value of the <literal>discriminator</literal> property.
+ </para>
+
+ </sect4>
+
+ <sect4>
+ <title>Class-specific Permission Configuration</title>
+
+ <para>
+ A further set of class-specific annotations can be used to configure a specific set of allowable permissions
+ for a target class. These permissions can be found in the <literal>org.jboss.seam.annotation.security.permission</literal>
+ package:
+ </para>
+
+ <table>
+ <title>Class Permission Annotations</title>
+
+ <tgroup cols="2">
+ <colspec colnum="1" colwidth="2*" />
+ <colspec colnum="2" colwidth="3*" />
+ <colspec colnum="3" colwidth="3*" />
+
+ <thead>
+ <row>
+ <entry align="center">
+ <para>Annotation</para>
+ </entry>
+ <entry align="center">
+ <para>Target</para>
+ </entry>
+ <entry align="center">
+ <para>Description</para>
+ </entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry>
+ <para>
+ <literal>@Permissions</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>TYPE</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ A container annotation, this annotation may contain an array of <literal>@Permission</literal> annotations.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>@Permission</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>TYPE</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ This annotation defines a single allowable permission action for the target class. Its <literal>action</literal>
+ property must be specified, and an optional <literal>mask</literal> property may also be specified if permission
+ actions are to be persisted as bitmasked values (see next section).
+ </para>
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ Here's an example of the above annotations in action. The following class can also be found in the SeamSpace example:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Permissions({
+ @Permission(action = "view"),
+ @Permission(action = "comment")
+})
+@Entity
+public class MemberImage implements Serializable {]]></programlisting>
+
+ <para>
+ This example demonstrates how two allowable permission actions, <literal>view</literal> and <literal>comment</literal>
+ can be declared for the entity class <literal>MemberImage</literal>.
+ </para>
+
+ </sect4>
+
+ <sect4>
+ <title>Permission masks</title>
+
+ <para>
+ By default, multiple permissions for the same target object and recipient will be persisted as a single database record,
+ with the <literal>action</literal> property/column containing a comma-separated list of the granted actions. To reduce
+ the amount of physical storage required to persist a large number of permissions, it is possible to use a bitmasked
+ integer value (instead of a comma-separated list) to store the list of permission actions.
+ </para>
+
+ <para>
+ For example, if recipient "Bob" is granted both the <literal>view</literal> and <literal>comment</literal> permissions
+ for a particular <literal>MemberImage</literal> (an entity bean) instance, then by default the <literal>action</literal> property of the
+ permission entity will contain "<literal>view,comment</literal>", representing the two granted permission actions.
+ Alternatively, if using bitmasked values for the permission actions, as defined like so:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Permissions({
+ @Permission(action = "view", mask = 1),
+ @Permission(action = "comment", mask = 2)
+})
+@Entity
+public class MemberImage implements Serializable {]]></programlisting>
+
+ <para>
+ The <literal>action</literal> property will instead simply contain "3" (with both the 1 bit and 2 bit switched on). Obviously
+ for a large number of allowable actions for any particular target class, the storage required for the permission records
+ is greatly reduced by using bitmasked actions.
+ </para>
+
+ <para>
+ Obviously, it is very important that the <literal>mask</literal> values specified are powers of 2.
+ </para>
+ </sect4>
+
+ <sect4>
+ <title>Identifier Policy</title>
+
+ <para>
+ When storing or looking up permissions, <literal>JpaPermissionStore</literal> must be able to uniquely identify specific
+ object instances to effectively operate on its permissions. To achieve this, an <emphasis>identifier strategy</emphasis>
+ may be assigned to each target class for the generation of unique identifier values. Each identifier strategy
+ implementation knows how to generate unique identifiers for a particular type of class, and it is a simple matter to
+ create new identifier strategies.
+ </para>
+
+ <para>
+ The <literal>IdentifierStrategy</literal> interface is very simple, declaring only two methods:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[public interface IdentifierStrategy {
+ boolean canIdentify(Class targetClass);
+ String getIdentifier(Object target);
+}]]></programlisting>
+
+ <para>
+ The first method, <literal>canIdentify()</literal> simply returns <literal>true</literal> if the identifier strategy
+ is capable of generating a unique identifier for the specified target class. The second method,
+ <literal>getIdentifier()</literal> returns the unique identifier value for the specified target object.
+ </para>
+
+ <para>
+ Seam provides two <literal>IdentifierStrategy</literal> implementations, <literal>ClassIdentifierStrategy</literal>
+ and <literal>EntityIdentifierStrategy</literal> (see next sections for details).
+ </para>
+
+ <para>
+ To explicitly configure a specific identifier strategy to use for a particular class, it should be annotated with
+ <literal>org.jboss.seam.annotations.security.permission.Identifier</literal>, and the value should be set to
+ a concrete implementation of the <literal>IdentifierStrategy</literal> interface. An optional <literal>name</literal>
+ property can also be specified, the effect of which is dependent upon the actual <literal>IdentifierStrategy</literal>
+ implementation used.
+ </para>
+ </sect4>
+
+ <sect4>
+ <title>ClassIdentifierStrategy</title>
+
+ <para>
+ This identifier strategy is used to generate unique identifiers for classes, and will use the value of the
+ <literal>name</literal> (if specified) in the <literal>@Identifier</literal> annotation. If there is no
+ <literal>name</literal> property provided, then it will attempt to use the component name of the class
+ (if the class is a Seam component), or as a last resort it will create an identifier based on the name
+ of the class (excluding the package name). For example, the identifier for the following class will
+ be "<literal>customer</literal>":
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Identifier(name = "customer")
+public class Customer {]]></programlisting>
+
+ <para>
+ The identifier for the following class will be "<literal>customerAction</literal>":
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Name("customerAction")
+public class CustomerAction { ]]></programlisting>
+
+ <para>
+ Finally, the identifier for the following class will be "<literal>Customer</literal>":
+ </para>
+
+ <programlisting><![CDATA[public class Customer { ]]></programlisting>
+
+ </sect4>
+
+ <sect4>
+ <title>EntityIdentifierStrategy</title>
+
+ <para>
+ This identifier strategy is used to generate unique identifiers for entity beans. It does so by
+ concatenating the entity name (or otherwise configured name) with a string representation of the
+ primary key value of the entity. The rules for generating the name section of the identifier are
+ similar to <literal>ClassIdentifierStrategy</literal>. The primary key value (i.e. the
+ <emphasis>id</emphasis> of the entity) is obtained using the <literal>PersistenceProvider</literal>
+ component, which is able to correctly determine the value regardless of which persistence implementation
+ is used within the Seam application. For entities not annotated with <literal>@Entity</literal>, it is
+ necessary to explicitly configure the identifier strategy on the entity class itself, for example:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Identifier(value = EntityIdentifierStrategy.class)
+public class Customer { ]]></programlisting>
+
+ <para>
+ For an example of the type of identifier values generated, assume we have the following entity class:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Entity
+public class Customer {
+ private Integer id;
+ private String firstName;
+ private String lastName;
+
+ @Id
+ public Integer getId() { return id; }
+ public void setId(Integer id) { this.id = id; }
+
+ public String getFirstName() { return firstName; }
+ public void setFirstName(String firstName) { this.firstName = firstName; }
+
+ public String getLastName() { return lastName; }
+ public void setLastName(String lastName) { this.lastName = lastName; }
+}]]></programlisting>
+
+ <para>
+ For a <literal>Customer</literal> instance with an <literal>id</literal> value of <literal>1</literal>,
+ the value of the identifier would be "<literal>Customer:1</literal>". If the entity class is annotated
+ with an explicit identifier name, like so:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Entity
+@Identifier(name = "cust")
+public class Customer { ]]></programlisting>
+
+ <para>
+ Then a <literal>Customer</literal> with an <literal>id</literal> value of <literal>123</literal>
+ would have an identifier value of "<literal>cust:123</literal>".
+ </para>
+ </sect4>
+
+ </sect3>
+
+ </sect2>
+
+ </sect1>
+
+ <sect1>
+ <title>Permission Management</title>
+
+ <para>
+ In much the same way that Seam Security provides an Identity Management API for the management of users and roles,
+ it also provides a Permissions Management API for the management of persistent user permissions, via the
+ <literal>PermissionManager</literal> component.
+ </para>
+
+ <sect2>
+ <title>PermissionManager</title>
+
+ <para>
+ The <literal>PermissionManager</literal> component is an application-scoped Seam component that provides a number of
+ methods for managing permissions. Before it can be used, it must be configured with a permission store (although by
+ default it will attempt to use <literal>JpaPermissionStore</literal> if it is available). To explicitly configure a
+ custom permission store, specify the <literal>permission-store</literal> property in components.xml:
+ </para>
+
+ <programlisting role="XML"><![CDATA[
+<security:permission-manager permission-store="#{ldapPermissionStore}"/>
+ ]]></programlisting>
+
+ <para>
+ The following table describes each of the available methods provided by <literal>PermissionManager</literal>:
+ </para>
+
+ <table>
+ <title>PermissionManager API methods</title>
+
+ <tgroup cols="2">
+ <colspec colnum="1" colwidth="2*" />
+ <colspec colnum="2" colwidth="3*" />
+ <colspec colnum="3" colwidth="3*" />
+
+ <thead>
+ <row>
+ <entry align="center">
+ <para>Return type</para>
+ </entry>
+ <entry align="center">
+ <para>Method</para>
+ </entry>
+ <entry align="center">
+ <para>Description</para>
+ </entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry>
+ <para>
+ <literal>List<Permission></literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>listPermissions(Object target, String action)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns a list of <literal>Permission</literal> objects representing all of the permissions that
+ have been granted for the specified target and action.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>List<Permission></literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>listPermissions(Object target)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns a list of <literal>Permission</literal> objects representing all of the permissions that
+ have been granted for the specified target and action.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>grantPermission(Permission permission)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Persists (grants) the specified <literal>Permission</literal> to the backend permission store.
+ Returns true if the operation was successful.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>grantPermissions(List<Permission> permissions)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Persists (grants) the specified list of <literal>Permission</literal>s to the backend permission store.
+ Returns true if the operation was successful.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>revokePermission(Permission permission)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Removes (revokes) the specified <literal>Permission</literal> from the backend permission store.
+ Returns true if the operation was successful.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>revokePermissions(List<Permission> permissions)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Removes (revokes) the specified list of <literal>Permission</literal>s from the backend permission store.
+ Returns true if the operation was successful.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>List<String></literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>listAvailableActions(Object target)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns a list of the available actions for the specified target object. The actions that this
+ method returns are dependent on the <literal>@Permission</literal> annotations configured on the
+ target object's class.
+ </para>
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect2>
+
+ <sect2>
+ <title>Permission checks for PermissionManager operations</title>
+
+ <para>
+ Invoking the methods of <literal>PermissionManager</literal> requires that the currently-authenticated user
+ has the appropriate authorization to perform that management operation. The following table lists the required
+ permissions that the current user must have.
+ </para>
+
+ <table>
+ <title>Permission Management Security Permissions</title>
+
+ <tgroup cols="2">
+ <colspec colnum="1" colwidth="1*" />
+ <colspec colnum="2" colwidth="3*" />
+
+ <thead>
+ <row>
+ <entry align="center">
+ <para>Method</para>
+ </entry>
+ <entry align="center">
+ <para>Permission Target</para>
+ </entry>
+ <entry align="center">
+ <para>Permission Action</para>
+ </entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry>
+ <para>
+ <literal>listPermissions()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ The specified <literal>target</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.read-permissions</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>grantPermission()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ The target of the specified <literal>Permission</literal>, or each of the targets
+ for the specified list of <literal>Permission</literal>s (depending on which method is
+ called).
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.grant-permission</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>grantPermission()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ The target of the specified <literal>Permission</literal>.
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.grant-permission</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>grantPermissions()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Each of the targets of the specified list of <literal>Permission</literal>s.
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.grant-permission</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>revokePermission()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ The target of the specified <literal>Permission</literal>.
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.revoke-permission</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>revokePermissions()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Each of the targets of the specified list of <literal>Permission</literal>s.
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.revoke-permission</literal>
+ </para>
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect2>
+
+ </sect1>
+
+ <sect1>
+ <title>SSL Security</title>
+
+ <para>
+ Seam includes basic support for serving sensitive pages via the HTTPS protocol. This is easily
+ configured by specifying a <literal>scheme</literal> for the page in <literal>pages.xml</literal>.
+ The following example shows how the view <literal>/login.xhtml</literal> is configured to use
+ HTTPS:
+ </para>
+
+ <programlisting role="XML"><![CDATA[<page view-id="/login.xhtml" scheme="https"/>]]></programlisting>
+
+ <para>
+ This configuration is automatically extended to both <literal>s:link</literal> and
+ <literal>s:button</literal> JSF controls, which (when specifying the <literal>view</literal>)
+ will also render the link using the correct protocol. Based on the previous example, the following
+ link will use the HTTPS protocol because <literal>/login.xhtml</literal> is configured to use it:
+ </para>
+
+ <programlisting role="XHTML"><![CDATA[<s:link view="/login.xhtml" value="Login"/>]]></programlisting>
+
+ <para>
+ Browsing directly to a view when using the <emphasis>incorrect</emphasis> protocol will cause a
+ redirect to the same view using the <emphasis>correct</emphasis> protocol. For example, browsing
+ to a page that has <literal>scheme="https"</literal> using HTTP will cause a redirect to the same
+ page using HTTPS.
+ </para>
+
+ <para>
+ It is also possible to configure a <emphasis>default scheme</emphasis> for all pages. This is useful
+ if you wish to use HTTPS for a only few pages. If no default scheme is specified then the normal
+ behavior is to continue use the current scheme. So once the user accessed a page that required
+ HTTPS, then HTTPS would continue to be used after the user navigated away to other non-HTTPS pages.
+ (While this is good for security, it is not so great for performance!). To define HTTP as the
+ default <literal>scheme</literal>, add this line to <literal>pages.xml</literal>:
+ </para>
+
+ <programlisting role="XML"><![CDATA[<page view-id="*" scheme="http" />]]></programlisting>
+
+ <para>
+ Of course, if <emphasis>none</emphasis> of the pages in your application use HTTPS then it is not
+ required to specify a default scheme.
+ </para>
+
+ <para>
+ You may configure Seam to automatically invalidate the current HTTP session each time the scheme
+ changes. Just add this line to <literal>components.xml</literal>:
+ </para>
+
+ <programlisting role="XML"><![CDATA[<web:session invalidate-on-scheme-change="true"/>]]></programlisting>
+
+ <para>
+ This option helps make your system less vulnerable to sniffing of the session id or leakage of
+ sensitive data from pages using HTTPS to other pages using HTTP.
+ </para>
+
+ <sect2>
+ <title>Overriding the default ports</title>
+
+ <para>
+ If you wish to configure the HTTP and HTTPS ports manually, they may be configured in
+ <literal>pages.xml</literal> by specifying the <literal>http-port</literal> and
+ <literal>https-port</literal> attributes on the <literal>pages</literal> element:
+ </para>
+
+ <programlisting role="XML"><![CDATA[
+<pages xmlns="http://jboss.com/products/seam/pages"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.2.xsd"
+ no-conversation-view-id="/home.xhtml"
+ login-view-id="/login.xhtml"
+ http-port="8080"
+ https-port="8443">
+ ]]></programlisting>
+ </sect2>
+
+ </sect1>
+
+ <sect1>
+ <title>CAPTCHA</title>
+
+ <para>
+ Though strictly not part of the security API, Seam provides a built-in CAPTCHA (<emphasis>C</emphasis>ompletely
+ <emphasis>A</emphasis>utomated <emphasis>P</emphasis>ublic <emphasis>T</emphasis>uring test to tell
+ <emphasis>C</emphasis>omputers and <emphasis>H</emphasis>umans <emphasis>A</emphasis>part) algorithm to
+ prevent automated processes from interacting with your application.
+ </para>
+
+ <sect2>
+ <title>Configuring the CAPTCHA Servlet</title>
+ <para>
+ To get up and running, it is necessary to configure the Seam Resource Servlet, which will provide the Captcha
+ challenge images to your pages. This requires the following entry in <literal>web.xml</literal>:
+ </para>
+
+ <programlisting role="XML"><![CDATA[<servlet>
+ <servlet-name>Seam Resource Servlet</servlet-name>
+ <servlet-class>org.jboss.seam.servlet.SeamResourceServlet</servlet-class>
+</servlet>
+
+<servlet-mapping>
+ <servlet-name>Seam Resource Servlet</servlet-name>
+ <url-pattern>/seam/resource/*</url-pattern>
+</servlet-mapping>]]></programlisting>
+
+ </sect2>
+
+ <sect2>
+ <title>Adding a CAPTCHA to a form</title>
+
+ <para>
+ Adding a CAPTCHA challenge to a form is extremely easy. Here's an example:
+ </para>
+
+ <programlisting role="XHTML"><![CDATA[<h:graphicImage value="/seam/resource/captcha"/>
+<h:inputText id="verifyCaptcha" value="#{captcha.response}" required="true">
+ <s:validate />
+</h:inputText>
+<h:message for="verifyCaptcha"/>]]></programlisting>
+
+ <para>
+ That's all there is to it. The <literal>graphicImage</literal> control displays the CAPTCHA challenge,
+ and the <literal>inputText</literal> receives the user's response. The response is automatically
+ validated against the CAPTCHA when the form is submitted.
+ </para>
+
+ </sect2>
+
+ <sect2>
+ <title>Customising the CAPTCHA algorithm</title>
+
+ <para>
+ You may customize the CAPTCHA algorithm by overriding the built-in component:
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Name("org.jboss.seam.captcha.captcha")
+@Scope(SESSION)
+public class HitchhikersCaptcha extends Captcha
+{
+ @Override @Create
+ public void init()
+ {
+ setChallenge("What is the answer to life, the universe and everything?");
+ setCorrectResponse("42");
+ }
+
+ @Override
+ public BufferedImage renderChallenge()
+ {
+ BufferedImage img = super.renderChallenge();
+ img.getGraphics().drawOval(5, 3, 60, 14); //add an obscuring decoration
+ return img;
+ }
+}]]></programlisting>
+
+ </sect2>
+
+ </sect1>
+
+ <sect1>
+ <title>Security Events</title>
+
+ <para>
+ The following table describes a number of events (see <xref linkend="events"/>) raised by Seam Security
+ in response to certain security-related events.
+ </para>
+
+ <table>
+ <title>Security Events</title>
+
+ <tgroup cols="2">
+ <colspec colnum="1" colwidth="3*" />
+ <colspec colnum="2" colwidth="2*" />
+
+ <thead>
+ <row>
+ <entry align="center">
+ <para>Event Key</para>
+ </entry>
+ <entry align="center">
+ <para>Description</para>
+ </entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry>
+ <para>
+ <literal>org.jboss.seam.security.loginSuccessful</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Raised when a login attempt is successful.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>org.jboss.seam.security.loginFailed</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Raised when a login attempt fails.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>org.jboss.seam.security.alreadyLoggedIn</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Raised when a user that is already authenticated attempts to log in again.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>org.jboss.seam.security.notLoggedIn</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Raised when a security check fails when the user is not logged in.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>org.jboss.seam.security.notAuthorized</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Raised when a security check fails when the user is logged in however doesn't have sufficient privileges.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>org.jboss.seam.security.preAuthenticate</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Raised just prior to user authentication.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>org.jboss.seam.security.postAuthenticate</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Raised just after user authentication.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>org.jboss.seam.security.loggedOut</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Raised after the user has logged out.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>org.jboss.seam.security.credentialsUpdated</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Raised when the user's credentials have been changed.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>org.jboss.seam.security.rememberMe</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Raised when the Identity's rememberMe property is changed.
+ </para>
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect1>
+
+ <sect1>
+ <title>Run As</title>
+
+ <para>
+ Sometimes it may be necessary to perform certain operations with elevated privileges, such
+ as creating a new user account as an unauthenticated user. Seam Security supports such a
+ mechanism via the <literal>RunAsOperation</literal> class. This class allows either the
+ <literal>Principal</literal> or <literal>Subject</literal>, or the user's roles to be
+ overridden for a single set of operations.
+ </para>
+
+ <para>
+ The following code example demonstrates how <literal>RunAsOperation</literal> is used, by
+ calling its <literal>addRole()</literal> method to provide a set of roles to masquerade
+ as for the duration of the operation. The <literal>execute()</literal> method contains the
+ code that will be executed with the elevated privileges.
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[ new RunAsOperation() {
+ public void execute() {
+ executePrivilegedOperation();
+ }
+ }.addRole("admin")
+ .run();]]></programlisting>
+
+ <para>
+ In a similar way, the <literal>getPrincipal()</literal> or <literal>getSubject()</literal>
+ methods can also be overriden to specify the <literal>Principal</literal> and
+ <literal>Subject</literal> instances to use for the duration of the operation.
+ Finally, the <literal>run()</literal> method is used to carry out the
+ <literal>RunAsOperation</literal>.
+ </para>
+
+ </sect1>
+
+ <sect1>
+ <title>Extending the Identity component</title>
+
+ <para>
+ Sometimes it might be necessary to extend the Identity component if your application has
+ special security requirements. The following example (contrived, as credentials would normally
+ be handled by the <literal>Credentials</literal> component instead) shows an extended Identity
+ component with an additional <literal>companyCode</literal> field. The install precendence of <literal>APPLICATION</literal>
+ ensures that this extended Identity gets installed in preference to the built-in Identity.
+ </para>
+
+ <programlisting role="JAVA"><![CDATA[@Name("org.jboss.seam.security.identity")
+@Scope(SESSION)
+@Install(precedence = APPLICATION)
+@BypassInterceptors
+@Startup
+public class CustomIdentity extends Identity
+{
+ private static final LogProvider log = Logging.getLogProvider(CustomIdentity.class);
+
+ private String companyCode;
+
+ public String getCompanyCode()
+ {
+ return companyCode;
+ }
+
+ public void setCompanyCode(String companyCode)
+ {
+ this.companyCode = companyCode;
+ }
+
+ @Override
+ public String login()
+ {
+ log.info("###### CUSTOM LOGIN CALLED ######");
+ return super.login();
+ }
+}]]></programlisting>
+
+ <warning>
+ <para>
+ Note that an <literal>Identity</literal> component must be marked <literal>@Startup</literal>, so that it is
+ available immediately after the <literal>SESSION</literal> context begins. Failing to do this may render
+ certain Seam functionality inoperable in your application.
+ </para>
+ </warning>
+
+
+ </sect1>
+
+
+ <sect1>
+ <title>OpenID</title>
+
+ <para>
+ OpenID is a community standard for external web-based authentication. The basic
+ idea is that any web application can supplement (or replace) its local handling of
+ authentication by delegating responsibility to an external OpenID server of the user's
+ chosing. This benefits the user, who no longer has to remember a name and password for
+ every web application he uses, and the developer, who is relieved of some of the burden of
+ maintaining a complex authentication system.
+ </para>
+
+ <para>When using OpenID, the user selects an OpenID provider, and the provider assigns the user
+ an OpenID. The id will take the form of a URL, for example <literal>http://maximoburrito.myopenid.com</literal> however,
+ it's acceptable to leave off the <literal>http://</literal> part of the identifier when logging into a site. The web application
+ (known as a relying party in OpenID-speak) determines which OpenID server to contact and redirects the user to the remote
+ site for authentication. Upon successful authentication the user is given
+ the (cryptographically secure) token proving his identity and is redirected back to the original web application.The
+ local web application can then be sure the user accessing the application controls the OpenID he presented.
+ </para>
+
+ <para>
+ It's important to realize at this
+ point that authentication does not imply authorization. The web application still needs to make a determination of how to
+ use that information. The web application could treat the user as instantly logged in and give full access to the system or
+ it could try and map the presented OpenID to a local user account, prompting the user to register if he hasn't already.
+ The choice of how to handle the OpenID is left as a design decision for the local application.
+ </para>
+
+
+ <sect2>
+ <title>Configuring OpenID</title>
+ <para>
+ Seam uses the openid4java package and requires four additional JARs to make use of the Seam integration. These
+ are: <literal>htmlparser.jar</literal>, <literal>openid4java.jar</literal>, <literal>openxri-client.jar</literal>
+ and <literal>openxri-syntax.jar</literal>.
+ </para>
+
+ <para>
+ OpenID processing requires the use of the <literal>OpenIdPhaseListener</literal>, which should be added to your
+ <literal>faces-config.xml</literal> file. The phase listener processes the callback from the OpenID provider, allowing
+ re-entry into the local application.
+ </para>
+
+ <programlisting role="XML"><lifecycle>
+ <phase-listener>org.jboss.seam.security.openid.OpenIdPhaseListener</phase-listener>
+</lifecycle></programlisting>
+
+
+ <para>
+ With this configuration, OpenID support is available to your application.
+ The OpenID support component, <literal>org.jboss.seam.security.openid.openid</literal>, is installed automatically if the openid4java
+ classes are on the classpath.
+ </para>
+ </sect2>
+
+ <sect2>
+ <title>Presenting an OpenIdDLogin form</title>
+
+ <para>
+ To initiate an OpenID login, you can present a simply form to the user asking for the user's OpenID. The <literal>#{openid.id}</literal>
+ value
+ accepts the user's OpenID and the <literal>#{openid.login}</literal> action initiates an authentication request.
+ </para>
+ <programlisting role="XML"><h:form>
+ <h:inputText value="#{openid.id}" />
+ <h:commandButton action="#{openid.login}" value="OpenID Login"/>
+</h:form></programlisting>
+
+ <para>
+ When the user submits the login form, he will be redirected to his OpenID provider. The user will eventually
+ return to your application through the Seam pseudo-view <literal>/openid.xhtml</literal>, which is
+ provided by the <literal>OpenIdPhaseListener</literal>. Your application can handle the OpenID response by means
+ of a <literal>pages.xml</literal> navigation from that view, just as if the user had never left your application.
+ </para>
+ </sect2>
+
+ <sect2>
+ <title>Logging in immediately</title>
+
+ <para> The simplest strategy is to simply login the user immediately. The following navigation rule shows how to handle this using
+ the <literal>#{openid.loginImmediately()}</literal> action.
+ </para>
+
+ <programlisting role="XML"><page view-id="/openid.xhtml">
+ <navigation evaluate="#{openid.loginImmediately()}">
+ <rule if-outcome="true">
+ <redirect view-id="/main.xhtml">
+ <message>OpenID login successful...</message>
+ </redirect>
+ </rule>
+ <rule if-outcome="false">
+ <redirect view-id="/main.xhtml">
+ <message>OpenID login rejected...</message>
+ </redirect>
+ </rule>
+ </navigation>
+</page></programlisting>
+
+ <para>Thie <literal>loginImmediately()</literal> action checks to see if the OpenID is valid. If it is valid, it adds an
+ OpenIDPrincipal to the identity component, marks the user as logged in (i.e. <literal>#{identity.loggedIn}</literal> will be true)
+ and returns true. If the OpenID was not validated, the method returns false, and the user re-enters the application un-authenticated.
+ If the user's OpenID is valid, it will be accessible using the expression <literal>#{openid.validatedId}</literal> and
+ <literal>#{openid.valid}</literal> will be true.
+ </para>
+
+
+ </sect2>
+
+ <sect2>
+ <title>Deferring login</title>
+
+ <para>
+ You may not want the user to be immediately logged in to your application. In that case, your navigation
+ should check the <literal>#{openid.valid}</literal> property and redirect the user to a local registration or processing
+ page. Actions you might take would be asking for more information and creating a local user account or presenting a captcha
+ to avoid programmatic registrations. When you are done processing, if you want to log the user in, you can call
+ the <literal>loginImmediately</literal> method, either through EL as shown previously or by directly interaction with the
+ <literal>org.jboss.seam.security.openid.OpenId</literal> component. Of course, nothing prevents you from writing custom
+ code to interact with the Seam identity component on your own for even more customized behaviour.
+ </para>
+
+ </sect2>
+
+
+ <sect2>
+ <title>Logging out</title>
+
+ <para>
+ Logging out (forgetting an OpenID association) is done by calling <literal>#{openid.logout}</literal>. If you
+ are not using Seam security, you can call this method directly. If you are using Seam security, you should
+ continue to use <literal>#{identity.logout}</literal> and install an event handler to capture the logout event, calling
+ the OpenID logout method.
+
+ </para>
+ <programlisting role="XML"><event type="org.jboss.seam.security.loggedOut">
+ <action execute="#{openid.logout}" />
+</event> </programlisting>
+
+ <para>It's important that you do not leave this out or the user will not be able to login again in the same session.</para>
+ </sect2>
+
+
+
+
+ </sect1>
+
+
+</chapter>
Property changes on: modules/security/trunk/docs/en-US/security-general.xml
___________________________________________________________________
Name: svn:executable
+ *
14 years, 7 months
Seam SVN: r12908 - in modules/security/trunk: impl/src/main/java/org/jboss/seam/security/management and 1 other directory.
by seam-commits@lists.jboss.org
Author: shane.bryzak(a)jboss.com
Date: 2010-05-30 19:52:25 -0400 (Sun, 30 May 2010)
New Revision: 12908
Modified:
modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityManager.java
modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityStore.java
modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/IdentityManagerImpl.java
modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java
Log:
update to match IdentityManager api
Modified: modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityManager.java
===================================================================
--- modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityManager.java 2010-05-30 12:09:36 UTC (rev 12907)
+++ modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityManager.java 2010-05-30 23:52:25 UTC (rev 12908)
@@ -222,14 +222,4 @@
* @return true if authentication was successful, false otherwise.
*/
boolean authenticate(String username, Credential credential);
-
- IdentityStore getIdentityStore();
-
- void setIdentityStore(IdentityStore identityStore);
-
- IdentityStore getRoleIdentityStore();
-
- void setRoleIdentityStore(IdentityStore roleIdentityStore);
-
- boolean isEnabled();
}
Modified: modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityStore.java
===================================================================
--- modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityStore.java 2010-05-30 12:09:36 UTC (rev 12907)
+++ modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityStore.java 2010-05-30 23:52:25 UTC (rev 12908)
@@ -122,6 +122,41 @@
boolean userExists(String username);
/**
+ *
+ * @param username
+ * @param attribute
+ * @param value
+ * @return
+ */
+ boolean setUserAttribute(String username, String attribute, Object value);
+
+ /**
+ *
+ * @param username
+ * @param attribute
+ * @return
+ */
+ boolean deleteUserAttribute(String username, String attribute);
+
+ /**
+ *
+ * @param username
+ * @param groupName
+ * @param groupType
+ * @return
+ */
+ boolean addUserToGroup(String username, String groupName, String groupType);
+
+ /**
+ *
+ * @param username
+ * @param groupName
+ * @param groupType
+ * @return
+ */
+ boolean removeUserFromGroup(String username, String groupName, String groupType);
+
+ /**
* Creates a new role type with the specified role type name.
* @return true if the role type was created successfully.
*/
@@ -132,20 +167,22 @@
*
* @param name The name of the user
* @param roleType The name of the role type to grant to the user.
- * @param group The name of the group to grant the role in
+ * @param groupName The name of the group to grant the role in
+ * @param groupType The group type
* @return true if the role was successfully granted.
*/
- boolean grantRole(String username, String roleType, Group group);
+ boolean grantRole(String username, String roleType, String groupName, String groupType);
/**
* Revokes the specified role from the specified user.
*
* @param name The name of the user
* @param roleType The name of the role type to revoke from the user.
- * @param group The name of the group which contains the user role
+ * @param groupName The name of the group which contains the user role
+ * @param groupType The group type
* @return true if the role was successfully revoked.
*/
- boolean revokeRole(String username, String roleType, Group group);
+ boolean revokeRole(String username, String roleType, String groupName, String groupType);
/**
* Deletes the specified role type.
@@ -199,15 +236,9 @@
* @return
*/
Group findGroup(String name, String groupType);
-
- /**
- * Returns a list of all user names.
- */
- List<String> findUsers();
/**
* Returns a list of all user names containing the specified filter text within their username.
-
*/
List<String> findUsers(String filter);
@@ -237,7 +268,7 @@
/**
* Lists the members of the specified role
*/
- List<IdentityType> listRoleMembers(String roleType, Group group);
+ List<IdentityType> listRoleMembers(String roleType, String groupName, String groupType);
/**
* Lists the members of the specified group
Modified: modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/IdentityManagerImpl.java
===================================================================
--- modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/IdentityManagerImpl.java 2010-05-30 12:09:36 UTC (rev 12907)
+++ modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/IdentityManagerImpl.java 2010-05-30 23:52:25 UTC (rev 12908)
@@ -105,18 +105,42 @@
return identityStore.isUserEnabled(name);
}
- public boolean grantRole(String name, String role, Group group)
+ public boolean setUserAttribute(String username, String attribute, Object value)
{
identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_UPDATE);
- return roleIdentityStore.grantRole(name, role, group);
+ return identityStore.setUserAttribute(username, attribute, value);
}
- public boolean revokeRole(String name, String role, Group group)
+ public boolean deleteUserAttribute(String username, String attribute)
{
identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_UPDATE);
- return roleIdentityStore.revokeRole(name, role, group);
+ return identityStore.deleteUserAttribute(username, attribute);
}
+ public boolean grantRole(String name, String role, String groupName, String groupType)
+ {
+ identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_UPDATE);
+ return roleIdentityStore.grantRole(name, role, groupName, groupType);
+ }
+
+ public boolean revokeRole(String name, String role, String groupName, String groupType)
+ {
+ identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_UPDATE);
+ return roleIdentityStore.revokeRole(name, role, groupName, groupType);
+ }
+
+ public boolean addUserToGroup(String username, String groupName, String groupType)
+ {
+ identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_UPDATE);
+ return identityStore.addUserToGroup(username, groupName, groupType);
+ }
+
+ public boolean removeUserFromGroup(String username, String groupName, String groupType)
+ {
+ identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_UPDATE);
+ return identityStore.removeUserFromGroup(username, groupName, groupType);
+ }
+
public boolean createRoleType(String roleType)
{
identity.checkPermission(ROLE_PERMISSION_NAME, PERMISSION_CREATE);
@@ -129,16 +153,16 @@
return roleIdentityStore.deleteRoleType(roleType);
}
- public boolean createGroup(Group group)
+ public boolean createGroup(String groupName, String groupType)
{
identity.checkPermission(GROUP_PERMISSION_NAME, PERMISSION_CREATE);
- return groupIdentityStore.createGroup(group.getName(), group.getGroupType());
+ return groupIdentityStore.createGroup(groupName, groupType);
}
- public boolean deleteGroup(Group group)
+ public boolean deleteGroup(String groupName, String groupType)
{
identity.checkPermission(GROUP_PERMISSION_NAME, PERMISSION_DELETE);
- return groupIdentityStore.deleteGroup(group.getName(), group.getGroupType());
+ return groupIdentityStore.deleteGroup(groupName, groupType);
}
public boolean removeFromGroup(String username, Group group)
@@ -156,21 +180,7 @@
{
return roleIdentityStore.roleTypeExists(roleType);
}
-
- public List<String> findUsers()
- {
- identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_READ);
- List<String> users = identityStore.findUsers();
- Collections.sort(users, new Comparator<String>() {
- public int compare(String value1, String value2) {
- return value1.compareTo(value2);
- }
- });
-
- return users;
- }
-
public List<String> findUsers(String filter)
{
identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_READ);
@@ -185,7 +195,7 @@
return users;
}
- public List<String> getRoleTypes()
+ public List<String> listRoleTypes()
{
identity.checkPermission(ROLE_PERMISSION_NAME, PERMISSION_READ);
List<String> roles = roleIdentityStore.listRoleTypes();
@@ -234,10 +244,10 @@
return roleIdentityStore.listImpliedRoles(username);
}
- public List<IdentityType> listRoleMembers(String roleType, Group group)
+ public List<IdentityType> listRoleMembers(String roleType, String groupName, String groupType)
{
identity.checkPermission(ROLE_PERMISSION_NAME, PERMISSION_READ);
- return roleIdentityStore.listRoleMembers(roleType, group);
+ return roleIdentityStore.listRoleMembers(roleType, groupName, groupType);
}
public boolean authenticate(String username, Credential credential)
Modified: modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java
===================================================================
--- modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java 2010-05-30 12:09:36 UTC (rev 12907)
+++ modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java 2010-05-30 23:52:25 UTC (rev 12908)
@@ -272,22 +272,22 @@
/**
*
*/
- @Inject PasswordEncoder passwordEncoder;
+ @Inject PasswordEncoder passwordEncoder;
-
- public boolean addUserToGroup(String username, Group group)
+ public boolean addUserToGroup(String username, String groupName,
+ String groupType)
{
// TODO Auto-generated method stub
return false;
}
- public boolean authenticate(String username, Credential credential)
+ public boolean addUserToGroup(String username, Group group)
{
// TODO Auto-generated method stub
return false;
}
- public boolean updateCredential(String username, Credential credential)
+ public boolean authenticate(String username, Credential credential)
{
// TODO Auto-generated method stub
return false;
@@ -305,7 +305,8 @@
return false;
}
- public boolean createUser(String username, Credential credential, Map<String,?> attributes)
+ public boolean createUser(String username, Credential credential,
+ Map<String, ?> attributes)
{
// TODO Auto-generated method stub
return false;
@@ -329,25 +330,25 @@
return false;
}
- public boolean disableUser(String username)
+ public boolean deleteUserAttribute(String username, String attribute)
{
// TODO Auto-generated method stub
return false;
}
- public boolean enableUser(String username)
+ public boolean disableUser(String username)
{
// TODO Auto-generated method stub
return false;
}
- public Group findGroup(String name, String groupType)
+ public boolean enableUser(String username)
{
// TODO Auto-generated method stub
- return null;
+ return false;
}
- public List<String> findUsers()
+ public Group findGroup(String name, String groupType)
{
// TODO Auto-generated method stub
return null;
@@ -359,7 +360,8 @@
return null;
}
- public boolean grantRole(String username, String roleType, Group group)
+ public boolean grantRole(String username, String roleType, String groupName,
+ String groupType)
{
// TODO Auto-generated method stub
return false;
@@ -395,7 +397,8 @@
return null;
}
- public List<IdentityType> listRoleMembers(String roleType, Group group)
+ public List<IdentityType> listRoleMembers(String roleType, String groupName,
+ String groupType)
{
// TODO Auto-generated method stub
return null;
@@ -407,13 +410,21 @@
return null;
}
+ public boolean removeUserFromGroup(String username, String groupName,
+ String groupType)
+ {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
public boolean removeUserFromGroup(String username, Group group)
{
// TODO Auto-generated method stub
return false;
}
- public boolean revokeRole(String username, String roleType, Group group)
+ public boolean revokeRole(String username, String roleType,
+ String groupName, String groupType)
{
// TODO Auto-generated method stub
return false;
@@ -425,15 +436,31 @@
return false;
}
+ public boolean setUserAttribute(String username, String attribute,
+ Object value)
+ {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
public boolean supportsFeature(Feature feature)
{
// TODO Auto-generated method stub
return false;
}
+ public boolean updateCredential(String username, Credential credential)
+ {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
public boolean userExists(String username)
{
// TODO Auto-generated method stub
return false;
- }
+ }
+
+
+
}
14 years, 7 months
Seam SVN: r12907 - in modules/security/trunk/impl: src/main/java/org/jboss/seam/security/management and 2 other directories.
by seam-commits@lists.jboss.org
Author: shane.bryzak(a)jboss.com
Date: 2010-05-30 08:09:36 -0400 (Sun, 30 May 2010)
New Revision: 12907
Removed:
modules/security/trunk/impl/src/main/java/org/jboss/seam/security/util/AnnotatedBeanProperty.java
Modified:
modules/security/trunk/impl/pom.xml
modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java
modules/security/trunk/impl/src/main/java/org/jboss/seam/security/permission/JpaPermissionStore.java
Log:
added weldx to project
Modified: modules/security/trunk/impl/pom.xml
===================================================================
--- modules/security/trunk/impl/pom.xml 2010-05-30 11:51:36 UTC (rev 12906)
+++ modules/security/trunk/impl/pom.xml 2010-05-30 12:09:36 UTC (rev 12907)
@@ -32,6 +32,12 @@
</dependency>
<dependency>
+ <groupId>org.jboss.weld</groupId>
+ <artifactId>weld-extensions</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
Modified: modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java
===================================================================
--- modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java 2010-05-30 11:51:36 UTC (rev 12906)
+++ modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/JpaIdentityStore.java 2010-05-30 12:09:36 UTC (rev 12907)
@@ -11,7 +11,7 @@
import org.jboss.seam.security.annotations.management.IdentityProperty;
import org.jboss.seam.security.annotations.management.PropertyType;
-import org.jboss.seam.security.util.AnnotatedBeanProperty;
+import org.jboss.weld.extensions.util.AnnotatedBeanProperty;
import org.picketlink.idm.api.Credential;
import org.picketlink.idm.api.Group;
import org.picketlink.idm.api.IdentityType;
Modified: modules/security/trunk/impl/src/main/java/org/jboss/seam/security/permission/JpaPermissionStore.java
===================================================================
--- modules/security/trunk/impl/src/main/java/org/jboss/seam/security/permission/JpaPermissionStore.java 2010-05-30 11:51:36 UTC (rev 12906)
+++ modules/security/trunk/impl/src/main/java/org/jboss/seam/security/permission/JpaPermissionStore.java 2010-05-30 12:09:36 UTC (rev 12907)
@@ -25,7 +25,7 @@
import org.jboss.seam.security.management.IdentityManager;
//import org.jboss.seam.security.management.JpaIdentityStore;
import org.jboss.seam.security.permission.PermissionMetadata.ActionSet;
-import org.jboss.seam.security.util.AnnotatedBeanProperty;
+import org.jboss.weld.extensions.util.AnnotatedBeanProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Deleted: modules/security/trunk/impl/src/main/java/org/jboss/seam/security/util/AnnotatedBeanProperty.java
===================================================================
--- modules/security/trunk/impl/src/main/java/org/jboss/seam/security/util/AnnotatedBeanProperty.java 2010-05-30 11:51:36 UTC (rev 12906)
+++ modules/security/trunk/impl/src/main/java/org/jboss/seam/security/util/AnnotatedBeanProperty.java 2010-05-30 12:09:36 UTC (rev 12907)
@@ -1,367 +0,0 @@
-package org.jboss.seam.security.util;
-
-import java.beans.Introspector;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Type;
-import java.lang.ExceptionInInitializerError;
-
-/**
- * A convenience class for working with an annotated property (either a field or method) of
- * a JavaBean class. By providing an isMatch() method in a concrete implementation
- * of this class, annotations may be matched on their attribute values or other
- * conditions.
- *
- * @author Shane Bryzak
- */
-public class AnnotatedBeanProperty<T extends Annotation>
-{
- private Field propertyField;
- private Method propertyGetter;
- private Method propertySetter;
- private String name;
- private Type propertyType;
- private T annotation;
-
- private boolean isFieldProperty;
- private boolean set = false;
-
- private Class<?> targetClass;
- private Class<T> annotationClass;
- private boolean scanned = false;
-
- /**
- * Default constructor
- *
- * @param cls The class to scan for the property
- * @param annotationClass The annotation class to scan for. Specified attribute
- * values may be scanned for by providing an implementation of the isMatch() method.
- */
- public AnnotatedBeanProperty(Class<?> cls, Class<T> annotationClass)
- {
- this.targetClass = cls;
- this.annotationClass = annotationClass;
- }
-
- /**
- * Scans the target class to locate the annotated property
- */
- private void scan()
- {
- // First check declared fields
- for (Field f : targetClass.getDeclaredFields())
- {
- if (f.isAnnotationPresent(annotationClass) &&
- isMatch(f.getAnnotation(annotationClass)))
- {
- setupFieldProperty(f);
- this.annotation = f.getAnnotation(annotationClass);
- set = true;
- return;
- }
- }
-
- // Then check public fields, in case it's inherited
- for (Field f : targetClass.getFields())
- {
- if (f.isAnnotationPresent(annotationClass) &&
- isMatch(f.getAnnotation(annotationClass)))
- {
- this.annotation = f.getAnnotation(annotationClass);
- setupFieldProperty(f);
- set = true;
- return;
- }
- }
-
- // Then check public methods (we ignore private methods)
- for (Method m : targetClass.getMethods())
- {
- if (m.isAnnotationPresent(annotationClass) &&
- isMatch(m.getAnnotation(annotationClass)))
- {
- this.annotation = m.getAnnotation(annotationClass);
- String methodName = m.getName();
-
- if ( m.getName().startsWith("get") )
- {
- this.name = Introspector.decapitalize( m.getName().substring(3) );
- }
- else if ( methodName.startsWith("is") )
- {
- this.name = Introspector.decapitalize( m.getName().substring(2) );
- }
-
- if (this.name != null)
- {
- this.propertyGetter = getGetterMethod(targetClass, this.name);
- this.propertySetter = getSetterMethod(targetClass, this.name);
- this.propertyType = this.propertyGetter.getGenericReturnType();
- isFieldProperty = false;
- set = true;
- }
- else
- {
- throw new IllegalStateException("Invalid accessor method, must start with 'get' or 'is'. " +
- "Method: " + m + " in class: " + targetClass);
- }
- }
- }
-
- scanned = true;
- }
-
- /**
- * This method may be overridden by a subclass. It can be used to scan
- * for an annotation with particular attribute values, or to allow a match
- * based on more complex logic.
- *
- * @param annotation The potential match
- * @return true if the specified annotation is a match
- */
- protected boolean isMatch(T annotation)
- {
- return true;
- }
-
- /**
- * This method sets the property value for a specified bean to the specified
- * value. The property to be set is either a field or setter method that
- * matches the specified annotation class and returns true for the isMatch()
- * method.
- *
- * @param bean The bean containing the property to set
- * @param value The new property value
- * @throws Exception
- */
- public void setValue(Object bean, Object value) throws Exception
- {
- if (!scanned) scan();
-
- if (isFieldProperty)
- {
- setFieldValue(propertyField, bean, value);
- }
- else
- {
- invokeMethod(propertySetter, bean, value);
- }
- }
-
- /**
- * Returns the property value for the specified bean. The property to be
- * returned is either a field or getter method that matches the specified
- * annotation class and returns true for the isMatch() method.
- *
- * @param bean The bean to read the property from
- * @return The property value
- * @throws Exception
- */
- public Object getValue(Object bean) throws Exception
- {
- if (!scanned) scan();
-
- if (isFieldProperty)
- {
- return getFieldValue(propertyField, bean);
- }
- else
- {
- return invokeMethod(propertyGetter, bean);
- }
- }
-
- /**
- * Returns the name of the property. If the property is a field, then the
- * field name is returned. Otherwise, if the property is a method, then the
- * name that is returned is the getter method name without the "get" or "is"
- * prefix, and a lower case first letter.
- *
- * @return The name of the property
- */
- public String getName()
- {
- if (!scanned) scan();
- return name;
- }
-
- /**
- * Returns the annotation type
- *
- * @return The annotation type
- */
- public T getAnnotation()
- {
- if (!scanned) scan();
- return annotation;
- }
-
- /**
- * Returns the property type
- *
- * @return The property type
- */
- public Type getPropertyType()
- {
- if (!scanned) scan();
- return propertyType;
- }
-
- /**
- * Returns true if the property has been successfully located, otherwise
- * returns false.
- *
- * @return
- */
- public boolean isSet()
- {
- if (!scanned) scan();
- return set;
- }
-
- private void setupFieldProperty(Field propertyField)
- {
- this.propertyField = propertyField;
- isFieldProperty = true;
- this.name = propertyField.getName();
- this.propertyType = propertyField.getGenericType();
- }
-
- private Object getFieldValue(Field field, Object obj)
- {
- field.setAccessible(true);
- try
- {
- return field.get(obj);
- }
- catch (IllegalAccessException e)
- {
- throw new RuntimeException(buildGetFieldValueErrorMessage(field, obj), e);
- }
- catch (NullPointerException ex)
- {
- NullPointerException ex2 = new NullPointerException(
- buildGetFieldValueErrorMessage(field, obj));
- ex2.initCause(ex.getCause());
- throw ex2;
- }
- }
-
- private String buildGetFieldValueErrorMessage(Field field, Object obj)
- {
- return String.format("Exception reading [%s] field from object [%s].",
- field.getName(), obj);
- }
-
- private void setFieldValue(Field field, Object obj, Object value)
- {
- field.setAccessible(true);
- try
- {
- field.set(obj, value);
- }
- catch (IllegalAccessException e)
- {
- throw new RuntimeException(buildSetFieldValueErrorMessage(field, obj, value), e);
- }
- catch (NullPointerException ex)
- {
- NullPointerException ex2 = new NullPointerException(
- buildSetFieldValueErrorMessage(field, obj, value));
- ex2.initCause(ex.getCause());
- throw ex2;
- }
- }
-
- private String buildSetFieldValueErrorMessage(Field field, Object obj, Object value)
- {
- return String.format("Exception setting [%s] field on object [%s] to value [%s]",
- field.getName(), obj, value);
- }
-
- private Object invokeMethod(Method method, Object obj, Object... args)
- {
- try
- {
- return method.invoke(obj, args);
- }
- catch (IllegalAccessException ex)
- {
- throw new RuntimeException(buildInvokeMethodErrorMessage(method, obj, args), ex);
- }
- catch (IllegalArgumentException ex)
- {
- throw new IllegalArgumentException(buildInvokeMethodErrorMessage(method, obj, args), ex.getCause());
- }
- catch (InvocationTargetException ex)
- {
- throw new RuntimeException(buildInvokeMethodErrorMessage(method, obj, args), ex);
- }
- catch (NullPointerException ex)
- {
- NullPointerException ex2 = new NullPointerException(buildInvokeMethodErrorMessage(method, obj, args));
- ex2.initCause(ex.getCause());
- throw ex2;
- }
- catch (ExceptionInInitializerError e)
- {
- throw new RuntimeException(buildInvokeMethodErrorMessage(method, obj, args), e);
- }
- }
-
- private String buildInvokeMethodErrorMessage(Method method, Object obj, Object... args)
- {
- StringBuilder message = new StringBuilder(String.format(
- "Exception invoking method [%s] on object [%s], using arguments [",
- method.getName(), obj));
- if (args != null) for (int i = 0; i < args.length; i++) message.append((i > 0 ? "," : "") + args[i]);
- message.append("]");
- return message.toString();
- }
-
- private Method getSetterMethod(Class<?> clazz, String name)
- {
- Method[] methods = clazz.getMethods();
- for (Method method: methods)
- {
- String methodName = method.getName();
- if ( methodName.startsWith("set") && method.getParameterTypes().length==1 )
- {
- if ( Introspector.decapitalize( methodName.substring(3) ).equals(name) )
- {
- return method;
- }
- }
- }
- throw new IllegalArgumentException("no such setter method: " + clazz.getName() + '.' + name);
- }
-
- private Method getGetterMethod(Class<?> clazz, String name)
- {
- Method[] methods = clazz.getMethods();
- for (Method method: methods)
- {
- String methodName = method.getName();
- if ( method.getParameterTypes().length==0 )
- {
- if ( methodName.startsWith("get") )
- {
- if ( Introspector.decapitalize( methodName.substring(3) ).equals(name) )
- {
- return method;
- }
- }
- else if ( methodName.startsWith("is") )
- {
- if ( Introspector.decapitalize( methodName.substring(2) ).equals(name) )
- {
- return method;
- }
- }
- }
- }
- throw new IllegalArgumentException("no such getter method: " + clazz.getName() + '.' + name);
- }
-}
\ No newline at end of file
14 years, 7 months
Seam SVN: r12906 - in modules/security/trunk: impl/src/main/java/org/jboss/seam/security/management and 1 other directory.
by seam-commits@lists.jboss.org
Author: shane.bryzak(a)jboss.com
Date: 2010-05-30 07:51:36 -0400 (Sun, 30 May 2010)
New Revision: 12906
Modified:
modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityManager.java
modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/IdentityManagerImpl.java
Log:
formalise IdentityManager interface
Modified: modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityManager.java
===================================================================
--- modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityManager.java 2010-05-29 03:06:00 UTC (rev 12905)
+++ modules/security/trunk/api/src/main/java/org/jboss/seam/security/management/IdentityManager.java 2010-05-30 11:51:36 UTC (rev 12906)
@@ -3,54 +3,186 @@
import java.util.List;
import org.picketlink.idm.api.Credential;
-import org.picketlink.idm.api.Group;
import org.picketlink.idm.api.IdentityType;
import org.picketlink.idm.api.Role;
/**
- * Identity Management API, deals with user name/password-based identity management.
+ * Identity Management API, allows management of users, groups and roles.
*
* @author Shane Bryzak
*/
public interface IdentityManager
{
+ /**
+ * Creates a new user with the specified username and credential.
+ *
+ * @param username The new user's username
+ * @param credential The new user's credential
+ * @return true if the user was successfully created, false otherwise.
+ */
boolean createUser(String username, Credential credential);
+ /**
+ * Deletes the user with the specified username. This operation also deletes
+ * all of the user's credentials, relationships and attributes.
+ *
+ * @param username The username of the user to delete
+ * @return true if the user was successfully deleted.
+ */
boolean deleteUser(String username);
+ /**
+ * Updates the credentials of the user with the specified username
+ *
+ * @param username The username of the user's credential to update
+ * @param credential The new credential
+ * @return true if the credential was successfully updated
+ */
+ boolean updateCredential(String username, Credential credential);
+
+ /**
+ * Checks if the user with the specified username exists
+ *
+ * @param username The username of the user
+ * @return true if the user exists
+ */
+ boolean userExists(String username);
+
+ /**
+ * Checks if a user account is currently enabled
+ *
+ * @param username The username of the user account to check
+ * @return true if the user account is enabled
+ */
+ boolean isUserEnabled(String username);
+
+ /**
+ * Enables the user account of the specified username
+ *
+ * @param username The username of the account to enable
+ * @return true if the account was successfully enabled
+ */
boolean enableUser(String username);
+ /**
+ * Disables the user account of the specified username
+ *
+ * @param username The username of the account to disable
+ * @return true if the account was successfully disabled
+ */
boolean disableUser(String username);
- boolean changePassword(String username, Credential credential);
+ /**
+ * Sets the specified attribute value for the specified user
+ *
+ * @param username The username of the user
+ * @param attribute The name of the attribute to set
+ * @param value The value of the attribute
+ * @return true if the attribute was successfully set
+ */
+ boolean setUserAttribute(String username, String attribute, Object value);
- boolean isUserEnabled(String username);
-
- boolean grantRole(String username, String roleType, Group group);
-
- boolean revokeRole(String username, String roleType, Group group);
-
+ /**
+ * Deletes the specified attribute value from the specified user
+ *
+ * @param username The username of the user
+ * @param attribute The name of the attribute to delete
+ * @return true if the attribute was successfully deleted
+ */
+ boolean deleteUserAttribute(String username, String attribute);
+
+ /**
+ * Creates a new role type
+ *
+ * @param roleType The name of the new role type
+ * @return true if the role type was successfully created
+ */
boolean createRoleType(String roleType);
+ /**
+ * Deletes the specified role type. All granted roles of the specified
+ * role type are deleted also.
+ *
+ * @param roleType The name of the role type to delete
+ * @return true if the role type was successfully deleted
+ */
boolean deleteRoleType(String roleType);
-
- boolean userExists(String username);
- boolean roleTypeExists(String roleType);
+ /**
+ * Creates a new group, with the specified name and of the specified group type
+ *
+ * @param name The name of the new group
+ * @param groupType The type of the new group
+ * @return true if the group was successfully created
+ */
+ boolean createGroup(String name, String groupType);
- boolean createGroup(Group group);
+ /**
+ * Deletes the group with the specified name and group type
+ *
+ * @param name The name of the group to delete
+ * @param groupType The type of the group to delete
+ * @return true if the group was successfully deleted
+ */
+ boolean deleteGroup(String name, String groupType);
- boolean deleteGroup(Group group);
+ /**
+ * Grants a role membership to the specified user.
+ *
+ * @param username The username of the user being granted role membership
+ * @param roleType The role type of the role being granted
+ * @param groupName The name of the group the role is being granted in
+ * @param groupType The type of the group
+ * @return true if the role was successfully granted
+ */
+ boolean grantRole(String username, String roleType, String groupName, String groupType);
- boolean addToGroup(String username, Group group);
+ /**
+ * Revokes role membership from the specified user.
+ *
+ * @param username The username of the user being revoked role membership
+ * @param roleType The role type of the role being revoked
+ * @param groupName The name of the group the role is being revoked from
+ * @param groupType The type of the group
+ * @return true if the role was successfully revoked
+ */
+ boolean revokeRole(String username, String roleType, String groupName, String groupType);
- boolean removeFromGroup(String username, Group group);
+ /**
+ * Adds a user to the specified group
+ *
+ * @param username The username of the user being added to the group
+ * @param groupName The name of the group the user is being added to
+ * @param groupType The type of the group
+ * @return true if the user was successfully added
+ */
+ boolean addUserToGroup(String username, String groupName, String groupType);
- List<String> findUsers();
+ /**
+ * Removes a user from the specified group
+ *
+ * @param username The username of the user being removed
+ * @param groupName The name of the group the user is being removed from
+ * @param groupType The type of the group
+ * @return true if the user was successfully removed
+ */
+ boolean removeUserFromGroup(String username, String groupName, String groupType);
+ /**
+ * Finds users that match the specified filter. A filter of null will return
+ * all users.
+ *
+ * @param filter The filter used to perform the search.
+ * @return A list of users that match the specified filter.
+ */
List<String> findUsers(String filter);
- List<String> getRoleTypes();
+ /**
+ * Returns a list of all the role types.
+ *
+ * @return A list of all role types
+ */
+ List<String> listRoleTypes();
List<String> getGrantableRoles();
@@ -70,9 +202,26 @@
*/
List<Role> getImpliedRoles(String name);
- List<IdentityType> listRoleMembers(String roleType, Group group);
+ /**
+ * Returns a list of all members that have been granted the specified role
+ *
+ * @param roleType The role type of the role
+ * @param groupName The name of the group the role has been granted in
+ * @param groupType The type of the group
+ * @return A List of IdentityType objects having membership of the specified role
+ */
+ List<IdentityType> listRoleMembers(String roleType, String groupName, String groupType);
- boolean authenticate(String username, String password);
+ /**
+ * Performs an authentication check using the specified username and credential.
+ * This operation does not establish any kind of security context, it simply
+ * returns a result indicating whether authentication is successful or not.
+ *
+ * @param username The username to authenticate
+ * @param credential The credential to authenticate with
+ * @return true if authentication was successful, false otherwise.
+ */
+ boolean authenticate(String username, Credential credential);
IdentityStore getIdentityStore();
Modified: modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/IdentityManagerImpl.java
===================================================================
--- modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/IdentityManagerImpl.java 2010-05-29 03:06:00 UTC (rev 12905)
+++ modules/security/trunk/impl/src/main/java/org/jboss/seam/security/management/IdentityManagerImpl.java 2010-05-30 11:51:36 UTC (rev 12906)
@@ -13,6 +13,7 @@
import org.jboss.seam.security.Identity;
import org.jboss.seam.security.util.Strings;
+import org.picketlink.idm.api.Credential;
import org.picketlink.idm.api.Group;
import org.picketlink.idm.api.IdentityType;
import org.picketlink.idm.api.Role;
@@ -68,15 +69,10 @@
}
}
- public boolean createUser(String name, String password)
+ public boolean createUser(String name, Credential credential)
{
- return createUser(name, password, null, null);
- }
-
- public boolean createUser(String name, String password, String firstname, String lastname)
- {
identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_CREATE);
- return identityStore.createUser(name, password, firstname, lastname);
+ return identityStore.createUser(name, credential, null);
}
public boolean deleteUser(String name)
@@ -97,10 +93,10 @@
return identityStore.disableUser(name);
}
- public boolean changePassword(String name, String password)
+ public boolean updateCredential(String name, Credential credential)
{
identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_UPDATE);
- return identityStore.changePassword(name, password);
+ return identityStore.updateCredential(name, credential);
}
public boolean isUserEnabled(String name)
@@ -133,30 +129,35 @@
return roleIdentityStore.deleteRoleType(roleType);
}
- public boolean createGroup(String name, String groupType)
+ public boolean createGroup(Group group)
{
identity.checkPermission(GROUP_PERMISSION_NAME, PERMISSION_CREATE);
- return groupIdentityStore.createGroup(name, groupType);
+ return groupIdentityStore.createGroup(group.getName(), group.getGroupType());
}
- public boolean deleteGroup(String name, String groupType)
+ public boolean deleteGroup(Group group)
{
identity.checkPermission(GROUP_PERMISSION_NAME, PERMISSION_DELETE);
- return groupIdentityStore.deleteGroup(name, groupType);
+ return groupIdentityStore.deleteGroup(group.getName(), group.getGroupType());
}
+ public boolean removeFromGroup(String username, Group group)
+ {
+ return groupIdentityStore.removeUserFromGroup(username, group);
+ }
+
public boolean userExists(String name)
{
identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_READ);
return identityStore.userExists(name);
}
- public boolean roleExists(String roleType)
+ public boolean roleTypeExists(String roleType)
{
return roleIdentityStore.roleTypeExists(roleType);
}
- public List<String> getUsers()
+ public List<String> findUsers()
{
identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_READ);
List<String> users = identityStore.findUsers();
@@ -170,7 +171,7 @@
return users;
}
- public List<String> getUsers(String filter)
+ public List<String> findUsers(String filter)
{
identity.checkPermission(USER_PERMISSION_NAME, PERMISSION_READ);
List<String> users = identityStore.findUsers(filter);
@@ -184,7 +185,7 @@
return users;
}
- public List<String> getRoles()
+ public List<String> getRoleTypes()
{
identity.checkPermission(ROLE_PERMISSION_NAME, PERMISSION_READ);
List<String> roles = roleIdentityStore.listRoleTypes();
@@ -239,10 +240,10 @@
return roleIdentityStore.listRoleMembers(roleType, group);
}
- public boolean authenticate(String username, String password)
+ public boolean authenticate(String username, Credential credential)
{
if (Strings.isEmpty(username)) return false;
- return identityStore.authenticate(username, password);
+ return identityStore.authenticate(username, credential);
}
public IdentityStore getIdentityStore()
14 years, 7 months
Seam SVN: r12905 - branches/enterprise/docs.
by seam-commits@lists.jboss.org
Author: laubai
Date: 2010-05-28 23:06:00 -0400 (Fri, 28 May 2010)
New Revision: 12905
Added:
branches/enterprise/docs/releases/
Log:
Creating releases directory for enterprise Seam documentation.
14 years, 7 months