Skip to content

Commit

Permalink
Refactor principal handling for UserDatabaseRealm
Browse files Browse the repository at this point in the history
Now extends GenericPrincipal for easier integration in the rest of
Tomcat.
  • Loading branch information
rmaucher committed Jun 2, 2021
1 parent 3af4901 commit d1ffc30
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 98 deletions.
160 changes: 62 additions & 98 deletions java/org/apache/catalina/realm/UserDatabaseRealm.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package org.apache.catalina.realm;

import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
Expand All @@ -29,7 +29,6 @@
import org.apache.catalina.Role;
import org.apache.catalina.User;
import org.apache.catalina.UserDatabase;
import org.apache.catalina.Wrapper;
import org.apache.naming.ContextBindings;
import org.apache.tomcat.util.ExceptionUtils;

Expand Down Expand Up @@ -113,69 +112,6 @@ public void setLocalJndiResource(boolean localJndiResource) {
}


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

/**
* Return <code>true</code> if the specified Principal has the specified
* security role, within the context of this Realm; otherwise return
* <code>false</code>. This implementation returns <code>true</code> if the
* <code>User</code> has the role, or if any <code>Group</code> that the
* <code>User</code> is a member of has the role.
*
* @param principal Principal for whom the role is to be checked
* @param role Security role to be checked
*/
@Override
public boolean hasRole(Wrapper wrapper, Principal principal, String role) {

UserDatabase database = getUserDatabase();
if (database == null) {
return false;
}

// Check for a role alias defined in a <security-role-ref> element
if (wrapper != null) {
String realRole = wrapper.findSecurityReference(role);
if (realRole != null) {
role = realRole;
}
}
if (principal instanceof GenericPrincipal) {
GenericPrincipal gp = (GenericPrincipal) principal;
if (gp.getUserPrincipal() instanceof UserDatabasePrincipal) {
principal = database.findUser(gp.getName());
}
}
if (!(principal instanceof User)) {
// Play nice with SSO and mixed Realms
// No need to pass the wrapper here because role mapping has been
// performed already a few lines above
return super.hasRole(null, principal, role);
}
if ("*".equals(role)) {
return true;
} else if (role == null) {
return false;
}
User user = (User) principal;
Role dbrole = database.findRole(role);
if (dbrole == null) {
return false;
}
if (user.isInRole(dbrole)) {
return true;
}
Iterator<Group> groups = user.getGroups();
while (groups.hasNext()) {
Group group = groups.next();
if (group.isInRole(dbrole)) {
return true;
}
}
return false;
}


// ------------------------------------------------------ Protected Methods

@Override
Expand Down Expand Up @@ -212,32 +148,7 @@ protected String getPassword(String username) {
*/
@Override
protected Principal getPrincipal(String username) {
UserDatabase database = getUserDatabase();
if (database == null) {
return null;
}

User user = database.findUser(username);
if (user == null) {
return null;
}

Set<String> roles = new HashSet<>();
Iterator<Role> uroles = user.getRoles();
while (uroles.hasNext()) {
Role role = uroles.next();
roles.add(role.getName());
}
Iterator<Group> groups = user.getGroups();
while (groups.hasNext()) {
Group group = groups.next();
uroles = group.getRoles();
while (uroles.hasNext()) {
Role role = uroles.next();
roles.add(role.getName());
}
}
return new GenericPrincipal(username, new ArrayList<>(roles), new UserDatabasePrincipal(username));
return new UserDatabasePrincipal(username);
}


Expand All @@ -261,7 +172,9 @@ private UserDatabase getUserDatabase() {
database = (UserDatabase) context.lookup(resourceName);
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
containerLog.error(sm.getString("userDatabaseRealm.lookup", resourceName), e);
if (containerLog != null) {
containerLog.error(sm.getString("userDatabaseRealm.lookup", resourceName), e);
}
database = null;
}
}
Expand Down Expand Up @@ -308,14 +221,65 @@ protected void stopInternal() throws LifecycleException {
}


private static class UserDatabasePrincipal implements Principal {
private final String name;
private UserDatabasePrincipal(String name) {
this.name = name;
public final class UserDatabasePrincipal extends GenericPrincipal {
private static final long serialVersionUID = 1L;
private final User user;

public UserDatabasePrincipal(String username) {
super(username);
UserDatabase database = getUserDatabase();
if (database == null) {
user = null;
} else {
user = database.findUser(username);
}
}

@Override
public String[] getRoles() {
if (user == null) {
return super.getRoles();
}
Set<String> roles = new HashSet<>();
Iterator<Role> uroles = user.getRoles();
while (uroles.hasNext()) {
Role role = uroles.next();
roles.add(role.getName());
}
Iterator<Group> groups = user.getGroups();
while (groups.hasNext()) {
Group group = groups.next();
uroles = group.getRoles();
while (uroles.hasNext()) {
Role role = uroles.next();
roles.add(role.getName());
}
}
return roles.toArray(new String[0]);
}

@Override
public String getName() {
return name;
public boolean hasRole(String role) {
if (user == null) {
return super.hasRole(role);
}
if ("*".equals(role)) {
return true;
} else if (role == null) {
return false;
}
Role dbrole = database.findRole(role);
if (dbrole == null) {
return false;
}
return user.isInRole(dbrole);
}

private Object writeReplace() {
// Replace with a static principal disconnected from the database
return new GenericPrincipal(getName(), Arrays.asList(getRoles()));
}

}

}
7 changes: 7 additions & 0 deletions test/org/apache/catalina/realm/TestGenericPrincipal.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ public void testSerialize03() throws ClassNotFoundException, IOException {
doTest(gpIn);
}

@Test
public void testSerialize04() throws ClassNotFoundException, IOException {
UserDatabaseRealm realm = new UserDatabaseRealm();
GenericPrincipal gpIn = realm.new UserDatabasePrincipal(USER);
doTest(gpIn);
}

private void doTest(GenericPrincipal gpIn)
throws ClassNotFoundException, IOException {
GenericPrincipal gpOut = serializeAndDeserialize(gpIn);
Expand Down
4 changes: 4 additions & 0 deletions webapps/docs/changelog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@
AprLifecycleListener does not show dev version suffix for libtcnative
and libapr. (michaelo)
</fix>
<update>
Refactor principal handling in <code>UserDatabaseRealm</code> using
an inner class that extends <code>GenericPrincipal</code>. (remm)
</update>
<fix>
Ignore duplicates when collecting the effective roles list from Roles
and Groups in <code>UserDatabaseRealm.getPrincipal(String)</code>.
Expand Down

0 comments on commit d1ffc30

Please sign in to comment.