Skip to content

Commit

Permalink
Add support for additional user attributes in TomcatPrincipal
Browse files Browse the repository at this point in the history
This closes apache#471
  • Loading branch information
cklein05 authored and michael-o committed Feb 8, 2022
1 parent 93deffc commit 8ced4ec
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 8 deletions.
32 changes: 32 additions & 0 deletions java/org/apache/catalina/TomcatPrincipal.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package org.apache.catalina;

import java.security.Principal;
import java.util.Collections;
import java.util.Enumeration;

import org.ietf.jgss.GSSCredential;

Expand Down Expand Up @@ -47,4 +49,34 @@ public interface TomcatPrincipal extends Principal {
* exception to LoginContext
*/
void logout() throws Exception;

/**
* Returns the value of the named attribute as an <code>Object</code>, or
* <code>null</code> if no attribute of the given name exists, or if
* <code>null</code> has been specified as the attribute's name.
* <p>
* Only the servlet container may set attributes to make available custom
* information about a Principal or the user it represents.
*
* @param name a <code>String</code> specifying the name of the attribute
* @return an <code>Object</code> containing the value of the attribute, or
* <code>null</code> if the attribute does not exist, or if
* <code>null</code> has been specified as the attribute's name
*/
default Object getAttribute(String name) {
return null;
}

/**
* Returns an <code>Enumeration</code> containing the names of the
* attributes available to this Principal. This method returns an empty
* <code>Enumeration</code> if the Principal has no attributes available to
* it.
*
* @return an <code>Enumeration</code> of strings containing the names of
* the Principal's attributes
*/
default Enumeration<String> getAttributeNames() {
return Collections.emptyEnumeration();
}
}
46 changes: 39 additions & 7 deletions java/org/apache/catalina/realm/GenericPrincipal.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
import java.io.Serializable;
import java.security.Principal;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;

import javax.security.auth.login.LoginContext;

Expand Down Expand Up @@ -120,7 +123,7 @@ public GenericPrincipal(String name, String password, List<String> roles,
*/
public GenericPrincipal(String name, List<String> roles,
Principal userPrincipal, LoginContext loginContext) {
this(name, roles, userPrincipal, loginContext, null);
this(name, roles, userPrincipal, loginContext, null, null);
}

/**
Expand All @@ -140,7 +143,7 @@ public GenericPrincipal(String name, List<String> roles,
@Deprecated
public GenericPrincipal(String name, String password, List<String> roles,
Principal userPrincipal, LoginContext loginContext) {
this(name, roles, userPrincipal, loginContext, null);
this(name, roles, userPrincipal, loginContext, null, null);
}

/**
Expand All @@ -154,10 +157,12 @@ public GenericPrincipal(String name, String password, List<String> roles,
* @param loginContext - If provided, this will be used to log out the user
* at the appropriate time
* @param gssCredential - If provided, the user's delegated credentials
* @param attributes - If provided, additional attributes associated with
* this Principal
*/
public GenericPrincipal(String name, List<String> roles,
Principal userPrincipal, LoginContext loginContext,
GSSCredential gssCredential) {
GSSCredential gssCredential, Map<String, Object> attributes) {
super();
this.name = name;
this.userPrincipal = userPrincipal;
Expand All @@ -171,6 +176,7 @@ public GenericPrincipal(String name, List<String> roles,
}
this.loginContext = loginContext;
this.gssCredential = gssCredential;
this.attributes = attributes != null ? Collections.unmodifiableMap(attributes) : null;
}


Expand All @@ -193,7 +199,7 @@ public GenericPrincipal(String name, List<String> roles,
public GenericPrincipal(String name, String password, List<String> roles,
Principal userPrincipal, LoginContext loginContext,
GSSCredential gssCredential) {
this(name, roles, userPrincipal, loginContext, gssCredential);
this(name, roles, userPrincipal, loginContext, gssCredential, null);
}


Expand Down Expand Up @@ -254,6 +260,11 @@ protected void setGssCredential(GSSCredential gssCredential) {
this.gssCredential = gssCredential;
}

/**
* The additional attributes associated with this Principal.
*/
protected final Map<String, Object> attributes;


// ---------------------------------------------------------- Public Methods

Expand Down Expand Up @@ -304,10 +315,28 @@ public void logout() throws Exception {
}


@Override
public Object getAttribute(String name) {
if (attributes == null || name == null) {
return null;
}
return attributes.get(name);
}


@Override
public Enumeration<String> getAttributeNames() {
if (attributes == null) {
return Collections.emptyEnumeration();
}
return Collections.enumeration(attributes.keySet());
}


// ----------------------------------------------------------- Serialization

private Object writeReplace() {
return new SerializablePrincipal(name, roles, userPrincipal);
return new SerializablePrincipal(name, roles, userPrincipal, attributes);
}

private static class SerializablePrincipal implements Serializable {
Expand All @@ -316,20 +345,23 @@ private static class SerializablePrincipal implements Serializable {
private final String name;
private final String[] roles;
private final Principal principal;
private final Map<String, Object> attributes;

public SerializablePrincipal(String name, String[] roles,
Principal principal) {
Principal principal, Map<String, Object> attributes) {
this.name = name;
this.roles = roles;
if (principal instanceof Serializable) {
this.principal = principal;
} else {
this.principal = null;
}
this.attributes = attributes;
}

private Object readResolve() {
return new GenericPrincipal(name, Arrays.asList(roles), principal);
return new GenericPrincipal(name, Arrays.asList(roles), principal, null, null,
attributes);
}
}
}
2 changes: 1 addition & 1 deletion java/org/apache/catalina/realm/JNDIRealm.java
Original file line number Diff line number Diff line change
Expand Up @@ -2516,7 +2516,7 @@ protected Principal getPrincipal(JNDIConnection connection, String username, GSS
}

if (user != null) {
return new GenericPrincipal(user.getUserName(), roles, null, null, gssCredential);
return new GenericPrincipal(user.getUserName(), roles, null, null, gssCredential, null);
}

return null;
Expand Down
5 changes: 5 additions & 0 deletions webapps/docs/changelog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@
Tomcat will not load from web applications. Pull request provided by
ppkarwasz. (markt)
</fix>
<add>
<pr>471</pr>: Add support for additional user attributes to
<code>TomcatPrincipal</code> and <code>GenericPrincipal</code>.
Patch provided by Carsten Klein. (michaelo)
</add>
</changelog>
</subsection>
<subsection name="Coyote">
Expand Down
44 changes: 44 additions & 0 deletions webapps/examples/jsp/security/protected/index.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
limitations under the License.
--%>
<%@ page import="java.util.Enumeration" %>
<%@ page import="java.security.Principal" %>
<%@ page import="org.apache.catalina.TomcatPrincipal" %>
<%
if (request.getParameter("logoff") != null) {
session.invalidate();
Expand Down Expand Up @@ -73,6 +75,48 @@ enter it here:
</form>
<br><br>

<%
Principal p = request.getUserPrincipal();
if (!(p instanceof TomcatPrincipal)) {
%>
<p>The principal does not support attributes.</p>
<%
} else {
TomcatPrincipal principal = (TomcatPrincipal) p;
%>
<p>The principal contains the following attributes:</p>
<table>
<tr><th>Name</th><th>Value</th><th>Type</th></tr>
<%
Enumeration<String> names = principal.getAttributeNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
Object value = principal.getAttribute(name);
String type = value != null ? value.getClass().getName() : "unknown";
if (value instanceof Object[]) {
Object[] values = (Object[]) value;
value = "";
for (int i = 0; i < values.length; i++) {
value += values[i] + "<br/>";
}
if (values.length > 0) {
type = values[0].getClass().getName() + "[]";
} else {
type = "unknown";
}
}
type = type.replaceFirst("^java\\.lang\\.", "");
%>
<tr><td><%= name %></td><td><%= value %></td><td><%= type %></td>
<%
}
%>
</table>
<%
}
%>
<br><br>

To add some data to the authenticated session, enter it here:
<form method="GET" action='<%= response.encodeURL("index.jsp") %>'>
<input type="text" name="dataName">
Expand Down

0 comments on commit 8ced4ec

Please sign in to comment.