This is an automated email from the ASF dual-hosted git repository.

smolnar82 pushed a commit to branch knox_idf
in repository https://gitbox.apache.org/repos/asf/knox.git

commit 542fb11789555484f4ae6d14be1ef890d41a0227
Author: lmccay <[email protected]>
AuthorDate: Tue Jun 16 11:12:59 2026 -0400

    KNOX-3347 - Introduce TokenExchangePrincipal for extending Act claim … 
(#1262)
    
    * KNOX-3347 - Introduce TokenExchangePrincipal for extending Act claim for 
token_exchange
    
    (cherry picked from commit 5d410895d452a6fe8b270a59ffce8d3a48237160)
---
 .../filter/AbstractIdentityAssertionFilter.java    |  23 ++++
 .../federation/jwt/filter/JWTFederationFilter.java | 151 ++++++++++++++++++++-
 .../apache/knox/gateway/security/SubjectUtils.java |  17 +++
 .../gateway/security/TokenExchangePrincipal.java   | 122 +++++++++++++++++
 .../security/TokenExchangePrincipalImpl.java       | 108 +++++++++++++++
 5 files changed, 416 insertions(+), 5 deletions(-)

diff --git 
a/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/AbstractIdentityAssertionFilter.java
 
b/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/AbstractIdentityAssertionFilter.java
index 4c212a534..81efb1511 100644
--- 
a/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/AbstractIdentityAssertionFilter.java
+++ 
b/gateway-provider-identity-assertion-common/src/main/java/org/apache/knox/gateway/identityasserter/common/filter/AbstractIdentityAssertionFilter.java
@@ -51,6 +51,7 @@ import org.apache.knox.gateway.security.GroupPrincipal;
 import org.apache.knox.gateway.security.ImpersonatedPrincipal;
 import org.apache.knox.gateway.security.PrimaryPrincipal;
 import org.apache.knox.gateway.security.SubjectUtils;
+import org.apache.knox.gateway.security.TokenExchangePrincipal;
 import org.apache.knox.gateway.security.TokenIdPrincipal;
 
 public abstract class AbstractIdentityAssertionFilter extends
@@ -106,6 +107,23 @@ public abstract class AbstractIdentityAssertionFilter 
extends
           throw new IllegalStateException("Required Subject Missing");
         }
 
+        // RFC 8693: Check if this is a token exchange request
+        // For token exchange, we need to apply local policy mapping to the 
subject identity
+        // BEFORE using it for impersonation. This ensures topology 
configuration takes precedence.
+        TokenExchangePrincipal tokenExchangePrincipal = 
SubjectUtils.getTokenExchangePrincipal(currentSubject);
+        if (tokenExchangePrincipal != null) {
+          // Get the subject identity from token exchange
+          String tokenExchangeSubject = 
tokenExchangePrincipal.getSubjectPrincipalName();
+
+          // Apply local policy mapping to the subject identity
+          // This allows topology configuration to map external identities to 
local ones
+          String mappedTokenExchangeSubject = 
mapUserPrincipal(tokenExchangeSubject);
+
+          // Use the mapped subject as the identity to impersonate
+          // This respects local policy while honoring the token exchange 
semantics
+          mappedPrincipalName = mappedTokenExchangeSubject;
+        }
+
         String primaryPrincipalName = 
SubjectUtils.getPrimaryPrincipalName(currentSubject);
         if (primaryPrincipalName != null) {
           if (!primaryPrincipalName.equals(mappedPrincipalName)) {
@@ -153,6 +171,11 @@ public abstract class AbstractIdentityAssertionFilter 
extends
           final Set<ActorChainPrincipal> actorChainPrincipals = 
SubjectUtils.getActorChainPrincipal(currentSubject, subject);
           subject.getPrincipals().addAll(actorChainPrincipals);
 
+          // RFC 8693 Token Exchange: Preserve TokenExchangePrincipal for 
audit trail
+          if (tokenExchangePrincipal != null) {
+            subject.getPrincipals().add(tokenExchangePrincipal);
+          }
+
           doAs(request, response, chain, subject);
         }
         else {
diff --git 
a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
 
b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
index ed207f86f..378c16a98 100644
--- 
a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
+++ 
b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
@@ -21,7 +21,10 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 import org.apache.knox.gateway.provider.federation.jwt.JWTMessages;
+import org.apache.knox.gateway.security.ActorChainPrincipalImpl;
 import org.apache.knox.gateway.security.PrimaryPrincipal;
+import org.apache.knox.gateway.security.TokenExchangePrincipal;
+import org.apache.knox.gateway.security.TokenExchangePrincipalImpl;
 import org.apache.knox.gateway.services.security.token.TokenUtils;
 import org.apache.knox.gateway.services.security.token.UnknownTokenException;
 import org.apache.knox.gateway.services.security.token.impl.JWT;
@@ -42,11 +45,13 @@ import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.security.Principal;
 import java.text.ParseException;
 import java.util.Base64;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Set;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
@@ -68,6 +73,7 @@ public class JWTFederationFilter extends AbstractJWTFilter {
   public static final String REFRESH_TOKEN_PARAM = "refresh_token";
   public static final String TOKEN_EXCHANGE = 
"urn:ietf:params:oauth:grant-type:token-exchange";
   public static final String SUBJECT_TOKEN = "subject_token";
+  public static final String ACTOR_TOKEN = "actor_token";
   public static final String CLIENT_ASSERTION_JWT_BEARER = 
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
   public static final String CLIENT_ASSERTION_TYPE = "client_assertion_type";
   public static final String CLIENT_ASSERTION = "client_assertion";
@@ -175,6 +181,15 @@ public class JWTFederationFilter extends AbstractJWTFilter 
{
       }
     }
 
+    // RFC 8693: Check if this is a token exchange request
+    HttpServletRequest httpRequest = (HttpServletRequest) request;
+    String grantType = httpRequest.getParameter(GRANT_TYPE);
+    if (TOKEN_EXCHANGE.equals(grantType)) {
+      // Handle RFC 8693 token exchange with subject_token and actor_token
+      handleTokenExchange(httpRequest, (HttpServletResponse) response, chain);
+      return;
+    }
+
     Pair<TokenType, String> wireToken = null;
     try {
       wireToken = getWireToken(request);
@@ -189,9 +204,9 @@ public class JWTFederationFilter extends AbstractJWTFilter {
 
       if (TokenType.JWT.equals(tokenType)) {
         try {
-          JWT token = new JWTToken(tokenValue);
-          if (validateToken((HttpServletRequest) request, 
(HttpServletResponse) response, chain, token)) {
-            final Subject subject = createSubjectFromToken(token);
+          JWT token = parseAndValidateJWT((HttpServletRequest) request, 
(HttpServletResponse) response, chain, tokenValue);
+          if (token != null) {
+            Subject subject = createSubjectFromToken(token);
             addKnoxIDFAttributes(request, token);
             continueWithEstablishedSecurityContext(subject, 
(HttpServletRequest) request, (HttpServletResponse) response, chain);
           }
@@ -396,8 +411,8 @@ public class JWTFederationFilter extends AbstractJWTFilter {
     final List<Cookie> relevantCookies = 
CookieUtils.getCookiesForName(request, cookieName);
     for (Cookie ssoCookie : relevantCookies) {
       try {
-        final JWT token = new JWTToken(ssoCookie.getValue());
-        if (validateToken(request, response, chain, token)) {
+        final JWT token = parseAndValidateJWT(request, response, chain, 
ssoCookie.getValue());
+        if (token != null) {
           final Subject subject = createSubjectFromToken(token);
           continueWithEstablishedSecurityContext(subject, request, response, 
chain);
           // we found a valid cookie we don't need to keep checking anymore
@@ -418,6 +433,132 @@ public class JWTFederationFilter extends 
AbstractJWTFilter {
     return false;
   }
 
+  /**
+   * Handle RFC 8693 token exchange flow.
+   *
+   * <p>This method validates both the subject_token and actor_token 
parameters,
+   * creates a TokenExchangePrincipal with the identity information from both 
tokens,
+   * and establishes a Subject with the actor as the PrimaryPrincipal.</p>
+   *
+   * <p>The TokenExchangePrincipal signals to the identity assertion layer that
+   * impersonation should be established with the subject as the 
ImpersonatedPrincipal.</p>
+   *
+   * @param request the HTTP request containing subject_token and actor_token 
parameters
+   * @param response the HTTP response
+   * @param chain the filter chain
+   * @throws IOException if an I/O error occurs
+   * @throws ServletException if a servlet error occurs
+   */
+  private void handleTokenExchange(HttpServletRequest request, 
HttpServletResponse response, FilterChain chain)
+      throws IOException, ServletException {
+    // Extract subject_token (required)
+    String subjectTokenValue = request.getParameter(SUBJECT_TOKEN);
+    if (subjectTokenValue == null || subjectTokenValue.isEmpty()) {
+      handleValidationError(request, response, 
HttpServletResponse.SC_BAD_REQUEST,
+          "RFC 8693 token exchange requires subject_token parameter");
+      return;
+    }
+
+    // Extract actor_token (required for proper token exchange)
+    String actorTokenValue = request.getParameter(ACTOR_TOKEN);
+    if (actorTokenValue == null || actorTokenValue.isEmpty()) {
+      handleValidationError(request, response, 
HttpServletResponse.SC_BAD_REQUEST,
+          "RFC 8693 token exchange requires actor_token parameter");
+      return;
+    }
+
+    try {
+      // Parse and validate subject_token
+      JWT subjectToken = parseAndValidateJWT(request, response, chain, 
subjectTokenValue);
+      if (subjectToken == null) {
+        // Validation failed, error response already sent
+        return;
+      }
+
+      // Parse and validate actor_token
+      JWT actorToken = parseAndValidateJWT(request, response, chain, 
actorTokenValue);
+      if (actorToken == null) {
+        // Validation failed, error response already sent
+        return;
+      }
+
+      // Create Subject with actor as PrimaryPrincipal and 
TokenExchangePrincipal
+      Subject subject = createSubjectForTokenExchange(subjectToken, 
actorToken);
+
+      continueWithEstablishedSecurityContext(subject, request, response, 
chain);
+
+    } catch (ParseException e) {
+      LOGGER.failedToParsePasscodeToken(e);
+      handleValidationError(request, response, 
HttpServletResponse.SC_UNAUTHORIZED,
+          "Failed to parse token in token exchange: " + e.getMessage());
+    }
+  }
+
+  /**
+   * Parse and validate a JWT token.
+   *
+   * @param request the HTTP request
+   * @param response the HTTP response
+   * @param chain the filter chain
+   * @param tokenValue the JWT string to parse
+   * @return the parsed and validated JWT, or null if validation failed
+   * @throws ParseException if the JWT cannot be parsed
+   * @throws IOException if an I/O error occurs during validation
+   * @throws ServletException if a servlet error occurs during validation
+   */
+  private JWT parseAndValidateJWT(HttpServletRequest request, 
HttpServletResponse response,
+                                  FilterChain chain, String tokenValue)
+      throws ParseException, IOException, ServletException {
+    JWT token = new JWTToken(tokenValue);
+    if (validateToken(request, response, chain, token)) {
+      return token;
+    }
+    // Validation failed - error response already sent by validateToken
+    return null;
+  }
+
+  /**
+   * Create a Subject for RFC 8693 token exchange with proper principal setup.
+   *
+   * @param subjectToken the validated subject token
+   * @param actorToken the validated actor token
+   * @return a Subject configured for token exchange
+   */
+  private Subject createSubjectForTokenExchange(JWT subjectToken, JWT 
actorToken) {
+    // Extract identities from the tokens
+    String subjectPrincipalName = subjectToken.getSubject();
+    String subjectIssuer = subjectToken.getIssuer();
+    String actorPrincipalName = actorToken.getSubject();
+    String actorIssuer = actorToken.getIssuer();
+    // Create principals for the Subject
+    // PrimaryPrincipal is the ACTOR (the authenticated party)
+    PrimaryPrincipal primaryPrincipal =
+        new PrimaryPrincipal(actorPrincipalName);
+
+    // TokenExchangePrincipal carries metadata for identity assertion layer
+    TokenExchangePrincipal tokenExchangePrincipal =
+        new TokenExchangePrincipalImpl(
+            subjectPrincipalName, subjectIssuer, actorPrincipalName, 
actorIssuer);
+
+    // Extract actor chain from subject_token (if present) using existing logic
+    List<Map<String, Object>> actorChain =
+        TokenUtils.extractActorChain(subjectToken);
+
+    // Create Subject with all necessary principals
+    Set<Principal> principals = new HashSet<>();
+    principals.add(primaryPrincipal);
+    principals.add(tokenExchangePrincipal);
+
+    // Add ActorChainPrincipal if actor chain exists in subject_token
+    if (!actorChain.isEmpty()) {
+      principals.add(new ActorChainPrincipalImpl(actorChain));
+    }
+
+    @SuppressWarnings("rawtypes")
+    HashSet emptySet = new HashSet();
+    return new Subject(true, principals, emptySet, emptySet);
+  }
+
   @Override
   protected void handleValidationError(HttpServletRequest request, 
HttpServletResponse response, int status,
                                        String error) throws IOException {
diff --git 
a/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java 
b/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java
index 11db9113f..9b6b50a62 100644
--- 
a/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java
+++ 
b/gateway-spi/src/main/java/org/apache/knox/gateway/security/SubjectUtils.java
@@ -105,4 +105,21 @@ public class SubjectUtils {
   public static Set<ActorChainPrincipal> getActorChainPrincipal(Subject 
currentSubject, Subject subject) {
     return currentSubject.getPrincipals(ActorChainPrincipal.class);
   }
+
+  /**
+   * Get the TokenExchangePrincipal from the subject if present.
+   *
+   * <p>This is used to detect when an RFC 8693 token exchange has occurred
+   * and to extract the subject and actor identities.</p>
+   *
+   * @param subject the subject to check
+   * @return the TokenExchangePrincipal if present, null otherwise
+   */
+  public static TokenExchangePrincipal getTokenExchangePrincipal(Subject 
subject) {
+    if (subject == null) {
+      return null;
+    }
+    Set<TokenExchangePrincipal> principals = 
subject.getPrincipals(TokenExchangePrincipal.class);
+    return principals.isEmpty() ? null : principals.iterator().next();
+  }
 }
diff --git 
a/gateway-spi/src/main/java/org/apache/knox/gateway/security/TokenExchangePrincipal.java
 
b/gateway-spi/src/main/java/org/apache/knox/gateway/security/TokenExchangePrincipal.java
new file mode 100644
index 000000000..835fa1450
--- /dev/null
+++ 
b/gateway-spi/src/main/java/org/apache/knox/gateway/security/TokenExchangePrincipal.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.security;
+
+import java.security.Principal;
+
+/**
+ * Principal that represents an RFC 8693 token exchange operation.
+ *
+ * <p>This principal carries metadata about both the subject and actor tokens
+ * through the authentication and identity assertion pipeline. It signals to
+ * downstream layers that a token exchange has occurred and provides the
+ * necessary information to establish proper impersonation semantics.</p>
+ *
+ * <p>In RFC 8693 token exchange:</p>
+ * <ul>
+ *   <li><b>Subject Token</b>: Represents the identity of the party on behalf
+ *       of whom the request is being made (will become 
ImpersonatedPrincipal)</li>
+ *   <li><b>Actor Token</b>: Represents the identity of the acting party who
+ *       is authorized to act (will become PrimaryPrincipal)</li>
+ * </ul>
+ *
+ * <p>This principal is added to the Subject during authentication/federation
+ * and is used by the identity assertion layer to establish the correct
+ * impersonation relationship using Subject.doAs().</p>
+ *
+ * <p>Example usage flow:</p>
+ * <pre>
+ * // In JWTFederationFilter (authentication layer):
+ * JWT subjectToken = validateSubjectToken(request);
+ * JWT actorToken = validateActorToken(request);
+ * TokenExchangePrincipal tep = new TokenExchangePrincipalImpl(
+ *     subjectToken.getSubject(), subjectToken.getIssuer(),
+ *     actorToken.getSubject(), actorToken.getIssuer()
+ * );
+ * subject.getPrincipals().add(tep);
+ *
+ * // In AbstractIdentityAssertionFilter (identity assertion layer):
+ * TokenExchangePrincipal tep = getTokenExchangePrincipal(subject);
+ * if (tep != null) {
+ *     // Set up impersonation: actor acts as subject
+ *     mappedPrincipalName = tep.getSubjectPrincipalName();
+ *     // This triggers existing impersonation logic
+ * }
+ * </pre>
+ *
+ * @see <a 
href="https://datatracker.ietf.org/doc/html/rfc8693#section-2.1";>RFC 8693 
Section 2.1</a>
+ */
+public interface TokenExchangePrincipal extends Principal {
+
+  /**
+   * Get the subject principal name (the identity on behalf of whom the 
request is made).
+   *
+   * <p>In the token exchange flow, this is extracted from the subject_token 
parameter.
+   * This identity will typically become the {@link ImpersonatedPrincipal} 
after the
+   * identity assertion layer processes the token exchange.</p>
+   *
+   * @return the subject principal name, never null
+   */
+  String getSubjectPrincipalName();
+
+  /**
+   * Get the issuer of the subject token.
+   *
+   * <p>Combined with the subject principal name, this provides a globally 
unique
+   * identity for the subject. This is important for audit trails and security
+   * decisions where the issuer context matters.</p>
+   *
+   * @return the subject token issuer, may be null if not available
+   */
+  String getSubjectIssuer();
+
+  /**
+   * Get the actor principal name (the identity of the acting party).
+   *
+   * <p>In the token exchange flow, this is extracted from the actor_token 
parameter.
+   * This identity represents the authenticated party who is authorized to act 
on
+   * behalf of the subject. This will typically be the {@link 
PrimaryPrincipal}.</p>
+   *
+   * @return the actor principal name, never null
+   */
+  String getActorPrincipalName();
+
+  /**
+   * Get the issuer of the actor token.
+   *
+   * <p>Combined with the actor principal name, this provides a globally unique
+   * identity for the actor. This is important for audit trails and security
+   * decisions where the issuer context matters.</p>
+   *
+   * @return the actor token issuer, may be null if not available
+   */
+  String getActorIssuer();
+
+  /**
+   * Returns the actor principal name.
+   *
+   * <p>This implements {@link Principal#getName()} and returns the actor's 
identity
+   * since the actor is the authenticated party performing the token 
exchange.</p>
+   *
+   * @return the actor principal name
+   */
+  @Override
+  default String getName() {
+    return getActorPrincipalName();
+  }
+}
diff --git 
a/gateway-spi/src/main/java/org/apache/knox/gateway/security/TokenExchangePrincipalImpl.java
 
b/gateway-spi/src/main/java/org/apache/knox/gateway/security/TokenExchangePrincipalImpl.java
new file mode 100644
index 000000000..01cfb48f1
--- /dev/null
+++ 
b/gateway-spi/src/main/java/org/apache/knox/gateway/security/TokenExchangePrincipalImpl.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.security;
+
+import java.util.Objects;
+
+/**
+ * Implementation of TokenExchangePrincipal that holds RFC 8693 token exchange 
metadata.
+ */
+public class TokenExchangePrincipalImpl implements TokenExchangePrincipal {
+  private final String subjectPrincipalName;
+  private final String subjectIssuer;
+  private final String actorPrincipalName;
+  private final String actorIssuer;
+
+  /**
+   * Creates a new TokenExchangePrincipal with the specified subject and actor 
identities.
+   *
+   * @param subjectPrincipalName the subject principal name (required)
+   * @param subjectIssuer the subject token issuer (may be null)
+   * @param actorPrincipalName the actor principal name (required)
+   * @param actorIssuer the actor token issuer (may be null)
+   * @throws IllegalArgumentException if subjectPrincipalName or 
actorPrincipalName is null
+   */
+  public TokenExchangePrincipalImpl(String subjectPrincipalName, String 
subjectIssuer,
+                                    String actorPrincipalName, String 
actorIssuer) {
+    if (subjectPrincipalName == null || subjectPrincipalName.isEmpty()) {
+      throw new IllegalArgumentException("Subject principal name cannot be 
null or empty");
+    }
+    if (actorPrincipalName == null || actorPrincipalName.isEmpty()) {
+      throw new IllegalArgumentException("Actor principal name cannot be null 
or empty");
+    }
+    this.subjectPrincipalName = subjectPrincipalName;
+    this.subjectIssuer = subjectIssuer;
+    this.actorPrincipalName = actorPrincipalName;
+    this.actorIssuer = actorIssuer;
+  }
+
+  @Override
+  public String getSubjectPrincipalName() {
+    return subjectPrincipalName;
+  }
+
+  @Override
+  public String getSubjectIssuer() {
+    return subjectIssuer;
+  }
+
+  @Override
+  public String getActorPrincipalName() {
+    return actorPrincipalName;
+  }
+
+  @Override
+  public String getActorIssuer() {
+    return actorIssuer;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("TokenExchangePrincipal[actor=");
+    sb.append(actorPrincipalName);
+    if (actorIssuer != null) {
+      sb.append("@").append(actorIssuer);
+    }
+    sb.append(", subject=").append(subjectPrincipalName);
+    if (subjectIssuer != null) {
+      sb.append("@").append(subjectIssuer);
+    }
+    sb.append("]");
+    return sb.toString();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof TokenExchangePrincipal)) {
+      return false;
+    }
+    TokenExchangePrincipal that = (TokenExchangePrincipal) o;
+    return Objects.equals(subjectPrincipalName, that.getSubjectPrincipalName())
+        && Objects.equals(subjectIssuer, that.getSubjectIssuer())
+        && Objects.equals(actorPrincipalName, that.getActorPrincipalName())
+        && Objects.equals(actorIssuer, that.getActorIssuer());
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(subjectPrincipalName, subjectIssuer, 
actorPrincipalName, actorIssuer);
+  }
+}

Reply via email to