Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for additional user attributes to DataSourceRealm #473

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
363 changes: 359 additions & 4 deletions java/org/apache/catalina/realm/DataSourceRealm.java

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions java/org/apache/catalina/realm/LocalStrings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ dataSourceRealm.commit=Exception committing connection before closing
dataSourceRealm.exception=Exception performing authentication
dataSourceRealm.getPassword.exception=Exception retrieving password for [{0}]
dataSourceRealm.getRoles.exception=Exception retrieving roles for [{0}]
dataSourceRealm.getUserAttributes.exception=Exception retrieving user attributes for [{0}]
dataSourceRealm.getAvailableUserAttributes.exception=Exception retrieving names of all available user attributes
dataSourceRealm.userAttributeNotFound=Specified user attribute [{0}] does not exist
dataSourceRealm.userAttributeAccessDenied=Access to specified user attribute [{0}] has been denied

jaasCallback.username=Returned username [{0}]

Expand Down
50 changes: 50 additions & 0 deletions java/org/apache/catalina/realm/RealmBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

Expand Down Expand Up @@ -76,6 +77,21 @@ public abstract class RealmBase extends LifecycleMBeanBase implements Realm {
private static final List<Class<? extends DigestCredentialHandlerBase>> credentialHandlerClasses =
new ArrayList<>();

/**
* The character used for delimiting user attribute names.
* <p>
* Applies to some of the Realm implementations only.
*/
protected static final String USER_ATTRIBUTES_DELIMITER = ",";

/**
* The character used as wildcard in user attribute lists. Using it means
* <i>query all available user attributes</i>.
* <p>
* Applies to some of the Realm implementations only.
*/
protected static final String USER_ATTRIBUTES_WILDCARD = "*";

static {
// Order is important since it determines the search order for a
// matching handler if only an algorithm is specified when calling
Expand Down Expand Up @@ -1293,6 +1309,40 @@ protected Server getServer() {
}


/**
* Parse the specified delimiter separated attribute names and return a list of
* that names or <code>null</code>, if no attributes have been specified.
* <p>
* If a wildcard character is found, return a list consisting of a single
* wildcard character only.
*
* @param userAttributes comma separated names of attributes to parse
* @return a list containing the parsed attribute names or <code>null</code>, if
* no attributes have been specified
*/
protected List<String> parseUserAttributes(String userAttributes) {
if (userAttributes == null) {
return null;
}
List<String> attrs = new ArrayList<>();
for (String name : userAttributes.split(USER_ATTRIBUTES_DELIMITER)) {
name = name.trim();
if (name.length() == 0) {
continue;
}
if (name.equals(USER_ATTRIBUTES_WILDCARD)) {
return Collections.singletonList(USER_ATTRIBUTES_WILDCARD);
}
if (attrs.contains(name)) {
// skip duplicates
continue;
}
attrs.add(name);
}
return attrs.size() > 0 ? attrs : null;
}


// --------------------------------------------------------- Static Methods

/**
Expand Down
4 changes: 4 additions & 0 deletions java/org/apache/catalina/realm/mbeans-descriptors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
type="java.lang.String"
writeable="false"/>

<attribute name="userAttributes"
description="Comma separated list of user attributes (columns) to additionally query from the user table"
type="java.lang.String"/>

<attribute name="userCredCol"
description="The column in the user table that holds the user's credentials"
type="java.lang.String"/>
Expand Down
4 changes: 4 additions & 0 deletions webapps/docs/changelog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@
<code>TomcatPrincipal</code> and <code>GenericPrincipal</code>.
Patch provided by Carsten Klein. (michaelo)
</add>
<add>
<pr>xxx</pr>: Add support for additional user attributes to
<code>DataSourceRealm</code>. Patch provided by Carsten Klein. (michaelo)
</add>
<fix>
<pr>469</pr>: Include the Jakarata Annotations API in the classes that
Tomcat will not load from web applications. Pull request provided by
Expand Down
35 changes: 34 additions & 1 deletion webapps/docs/config/realm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,43 @@
name. If not specified, the default is <code>true</code>.</p>
</attribute>

<attribute name="userAttributes" required="false">
<p>Comma separated list of names of columns to additionally query from
the "users" table named by the <code>userTable</code> attribute. These
are provided to the application as <i>Additional User Attributes</i>
through the Principal's attributes map (with configured column names
used as the keys). This attribute also supports the wildcard character
<code>*</code>, which means to query <i>all</i> columns (except, for
security reasons, the column containing the user's credentials).</p>
<p>Any of the "users" table's columns can be specified, except the
column containing the user's credentials (i.e. password) named by the
<code>userCredCol</code> attribute. If the credentials column is
explicitly specified, it will be ignored and a warning will be logged.
</p>
<p>Specified columns that are not present in the "users" table are
dropped from the list and a warning is logged, one for each missing
column. However, these checks are performed only once while the Realm
processes its first login attempt. If the structure of the "users"
table significantly changes after that point in time (i.e. a specified
column gets removed), a logged exception is likely the result for each
subsequent login attempt until the Realm gets restarted. Even so,
exceptions caused by user attribute queries do not prevent the user
from loggin in, but only cause the associated Principal's attributes
map being empty.</p>
<p>See the <a href="../realm-howto.html#Additional_User_Attributes">
Additional User Attributes How-To</a> for more information on how
additional user attributes can be access from your application.</p>
</attribute>

<attribute name="userCredCol" required="true">
<p>Name of the column, in the "users" table, which contains
the user's credentials (i.e. password). If a
<code>CredentialHandler</code> is specified, this component
will assume that the passwords have been encoded with the
specified algorithm. Otherwise, they will be assumed to be
in clear text.</p>
<p>For security reasons, the column named by this attribute cannot
be used as an <i>Additional User Attribute</i>.</p>
</attribute>

<attribute name="userNameCol" required="true">
Expand All @@ -192,7 +222,10 @@
<attribute name="userTable" required="true">
<p>Name of the "users" table, which must contain columns named
by the <code>userNameCol</code> and <code>userCredCol</code>
attributes.</p>
attributes. More columns containing additional user information could
freely be added. These can be provided to the application as
<i>Additional User Attributes</i> configured by the
<code>userAttributes</code> attribute.</p>
</attribute>

<attribute name="X509UsernameRetrieverClassName" required="false">
Expand Down
93 changes: 92 additions & 1 deletion webapps/docs/realm-howto.xml
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,95 @@ simplified to <code>{digest}</code>.</p>
</subsection>


<subsection name="Additional User Attributes">

<p>Some of the Realm implementations, currently only <code>DataSourceRealm</code>,
support a feature called <i>Additional User Attributes</i>. With that, the
Realm additionally queries a configurable list of attributes from the user's
entry in the associated user database. The purpose of this feature is to provide
extra user-related information, like user dispay name, e-mail address, department,
room number, phone number etc., to the application with only a configured list of
attribute names (like the SELECT clause of an SQL statement).</p>

<p>Additional user attributes are provided to the application through the
<code>Principal</code> instance, which is associated with each authenticated
request. Principals created by the <code>DataSourceRealm</code> maintain a set of
read-only <i>named attributes</i>, accessible by these methods:

<source>java.lang.Object getAttribute(java.lang.String name);

java.util.Enumeration&lt;java.lang.String&gt; getAttributeNames();
</source>

This set of attributes is populated by the Realm with the queried additional
user attributes. The configured names of the attributes to query additionally
also serve as the names of the attributes. These are the real names of the user
table's columns in the JDBC database.</p>

<p>The Principal returned for a request by method
<code>HttpServletRequest.getUserPrincipal()</code> is of type
<code>java.security.Principal</code>. The methods to access the Principal's
attributes, however, are actually declared in the
<code>org.apache.catalina.TomcatPrincipal</code> interface, which extends
<code>java.security.Principal</code> and is the base type for all Principal
instances used in Tomcat. So, in order to actually access the Principal's
attributes, an <code>instanceof</code> check and casting is required. Have a
look at this simple example, which dumps all available additional user
attributes:

<source>Principal p = request.getUserPrincipal();
if (p instanceof TomcatPrincipal) {

TomcatPrincipal principal = (TomcatPrincipal) p;

out.writeln("Principal contains these attributes:");

Enumeration&lt;String&gt; names = principal.getAttributeNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
Object value = principal.getAttribute(name);

out.writeln(name + ": " + value.toString());
}
} else {
out.writeln("Principal does not support attributes.")
}
</source>

The JSP example application referred to in the next section also lists the
Principal's attributes. However, in order to get any user attributes, ensure
to use <a href="config/realm.html#DataSource_Database_Realm_-_org.apache.catalina.realm.DataSourceRealm">DataSourceRealm</a>
with the example application and configure its <code>userAttributes</code>
attribute accordingly:</p>

<p>An example SQL script to create an attributes-extended user table might
look something like this (adapt the syntax as required for your particular
database):</p>
<source>create table users (
user_name varchar(15) not null primary key,
user_pass varchar(15) not null,
user_disp_name varchar(30),
user_dept varchar(15),
user_room integer,
user_phone varchar(15)
);</source>

<p>With that user table, the Realm's <code>userAttributes</code> attribute could
look like:</p>
<source><![CDATA[<Realm className="org.apache.catalina.realm.DataSourceRealm"
dataSourceName="jdbc/authority"
...
userAttributes="user_disp_name, user_dept, user_room, user_phone"
...
/>]]></source>

<p><strong>Security alert:</strong> Please note, that this feature, when
configured inappropriately and, depending on the user related data available in
the Realm's user database, could leak sensitive user data.
</p>

</subsection>


<subsection name="Example Application">

Expand Down Expand Up @@ -363,7 +452,9 @@ configuration documentation.</p>
<h5>Example</h5>

<p>An example SQL script to create the needed tables might look something
like this (adapt the syntax as required for your particular database):</p>
like this (adapt the syntax as required for your particular database). You may
add more columns to the "users" table to be provided as
<a href="#Additional_User_Attributes">Additional User Attributes</a>:</p>
<source>create table users (
user_name varchar(15) not null primary key,
user_pass varchar(15) not null
Expand Down