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

ChenSammi pushed a commit to branch HDDS-13323-sts
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/HDDS-13323-sts by this push:
     new dc3b37d5701 HDDS-15984. [STS] Improve s3:prefix Condition handling and 
reject unsupported AssumeRole parameters (#10875)
dc3b37d5701 is described below

commit dc3b37d57017d016f194947d469c0983e338cef0
Author: fmorg-git <[email protected]>
AuthorDate: Thu Jul 30 21:43:16 2026 -0700

    HDDS-15984. [STS] Improve s3:prefix Condition handling and reject 
unsupported AssumeRole parameters (#10875)
---
 .../security/acl/iam/IamSessionPolicyResolver.java |   7 +-
 .../acl/iam/TestIamSessionPolicyResolver.java      |  30 +++
 .../apache/hadoop/ozone/s3sts/S3STSEndpoint.java   | 173 +++++++++++++---
 .../hadoop/ozone/s3sts/S3STSEndpointBase.java      |  10 +
 .../hadoop/ozone/s3sts/TestS3STSEndpoint.java      | 227 ++++++++++++++++++++-
 5 files changed, 419 insertions(+), 28 deletions(-)

diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/iam/IamSessionPolicyResolver.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/iam/IamSessionPolicyResolver.java
index 4e591d14b5f..415ae5e8af7 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/iam/IamSessionPolicyResolver.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/iam/IamSessionPolicyResolver.java
@@ -309,7 +309,12 @@ private static Condition 
parsePrefixesFromConditions(JsonNode stmt) throws OMExc
             ERROR_PREFIX + "Invalid Condition operator value structure - " + 
operatorValue, MALFORMED_POLICY_DOCUMENT);
       }
 
-      final String keyName = operatorValue.fieldNames().hasNext() ? 
operatorValue.fieldNames().next() : null;
+      if (operatorValue.size() != 1) {
+        throw new OMException(
+            ERROR_PREFIX + "Only one Condition key is supported per operator", 
NOT_SUPPORTED_OPERATION);
+      }
+
+      final String keyName = operatorValue.fieldNames().next();
       if (!"s3:prefix".equalsIgnoreCase(keyName)) {
         throw new OMException(ERROR_PREFIX + "Unsupported Condition key name - 
" + keyName, NOT_SUPPORTED_OPERATION);
       }
diff --git 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/iam/TestIamSessionPolicyResolver.java
 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/iam/TestIamSessionPolicyResolver.java
index 2ac9d195cf5..2902aa4fba0 100644
--- 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/iam/TestIamSessionPolicyResolver.java
+++ 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/iam/TestIamSessionPolicyResolver.java
@@ -138,6 +138,36 @@ public void testUnsupportedConditionAttributeThrows() {
         json, "IAM session policy: Unsupported Condition key name - 
aws:SourceArn", NOT_SUPPORTED_OPERATION);
   }
 
+  @Test
+  public void testMultipleConditionKeysWithPrefixFirstThrows() {
+    final String json = "{\n" +
+        "  \"Statement\": [{\n" +
+        "    \"Effect\": \"Allow\",\n" +
+        "    \"Action\": \"s3:ListBucket\",\n" +
+        "    \"Resource\": \"arn:aws:s3:::b\",\n" +
+        "    \"Condition\": { \"StringEquals\": { \"s3:prefix\": \"x\", 
\"aws:SourceIp\": \"1.2.3.4\" } }\n" +
+        "  }]\n" +
+        "}";
+
+    expectResolveThrowsForBothAuthorizers(
+        json, "IAM session policy: Only one Condition key is supported per 
operator", NOT_SUPPORTED_OPERATION);
+  }
+
+  @Test
+  public void testMultipleConditionKeysWithUnsupportedKeyFirstThrows() {
+    final String json = "{\n" +
+        "  \"Statement\": [{\n" +
+        "    \"Effect\": \"Allow\",\n" +
+        "    \"Action\": \"s3:ListBucket\",\n" +
+        "    \"Resource\": \"arn:aws:s3:::b\",\n" +
+        "    \"Condition\": { \"StringEquals\": { \"aws:SourceIp\": 
\"1.2.3.4\", \"s3:prefix\": \"x\" } }\n" +
+        "  }]\n" +
+        "}";
+
+    expectResolveThrowsForBothAuthorizers(
+        json, "IAM session policy: Only one Condition key is supported per 
operator", NOT_SUPPORTED_OPERATION);
+  }
+
   @Test
   public void testUnsupportedEffectThrows() {
     final String json = "{\n" +
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpoint.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpoint.java
index 1cc92230149..365115a4aa9 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpoint.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpoint.java
@@ -28,27 +28,34 @@
 import static 
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.STS_VALIDATION_ERROR;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.time.Instant;
 import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import javax.inject.Inject;
-import javax.ws.rs.FormParam;
+import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Form;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBException;
 import javax.xml.bind.Marshaller;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Strings;
 import org.apache.hadoop.ozone.audit.S3GAction;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
@@ -88,6 +95,19 @@ public class S3STSEndpoint extends S3STSEndpointBase {
 
   private static final String EXPECTED_VERSION = "2011-06-15";
 
+  private static final String SIGV4_PARAM_PREFIX = "X-Amz-";
+
+  private static final Set<String> ASSUME_ROLE_ALLOWED_PARAMS = 
ImmutableSet.of(
+      "Action", "RoleArn", "RoleSessionName", "DurationSeconds", "Version", 
"Policy");
+
+  private static final String POLICY_ARNS_MEMBER_PREFIX = "PolicyArns.member.";
+  private static final String PROVIDED_CONTEXTS_MEMBER_PREFIX = 
"ProvidedContexts.member.";
+  private static final String TAGS_MEMBER_PREFIX = "Tags.member.";
+  private static final String TRANSITIVE_TAG_KEYS_MEMBER_PREFIX = 
"TransitiveTagKeys.member.";
+
+  private static final Set<String> AWS_VALID_ASSUME_ROLE_OPTIONAL_PARAMS = 
ImmutableSet.of(
+      "ExternalId", "SerialNumber", "SourceIdentity", "TokenCode");
+
   // JAXBContext is relatively expensive to create and is threadsafe, so cache 
and reuse
   private static final JAXBContext JAXB_CONTEXT;
 
@@ -128,49 +148,51 @@ public Response get(
       @QueryParam("Version") String version,
       @QueryParam("Policy") String awsIamSessionPolicy) throws OS3Exception {
 
-    return handleSTSRequest(action, roleArn, roleSessionName, durationSeconds, 
version, awsIamSessionPolicy);
+    return handleSTSRequest(
+        getQueryParameters().keySet(), action, roleArn, roleSessionName, 
durationSeconds, version, awsIamSessionPolicy);
   }
 
   /**
    * STS endpoint that handles POST requests with form data.
    * AWS STS typically uses POST requests with form-encoded parameters.
    *
-   * @param action The STS action to perform
-   * @param roleArn The ARN of the role to assume
-   * @param roleSessionName Session name for the role
-   * @param durationSeconds Duration of the token validity
-   * @param version AWS STS API version
+   * @param form form-encoded request parameters
    * @return Response containing STS response XML or error
    */
   @POST
+  @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
   @Produces(MediaType.APPLICATION_XML)
-  public Response post(
-      @FormParam("Action") String action,
-      @FormParam("RoleArn") String roleArn,
-      @FormParam("RoleSessionName") String roleSessionName,
-      @FormParam("DurationSeconds") Integer durationSeconds,
-      @FormParam("Version") String version,
-      @FormParam("Policy") String awsIamSessionPolicy) throws OS3Exception {
-
-    return handleSTSRequest(action, roleArn, roleSessionName, durationSeconds, 
version, awsIamSessionPolicy);
+  public Response post(Form form) throws OS3Exception {
+    if (form == null) {
+      return unknownOperationExceptionResponse();
+    }
+
+    final MultivaluedMap<String, String> formParams = form.asMap();
+    final String action = formParams.getFirst("Action");
+    final String roleArn = formParams.getFirst("RoleArn");
+    final String roleSessionName = formParams.getFirst("RoleSessionName");
+    final Integer durationSeconds = 
parseIntegerOrNull(formParams.getFirst("DurationSeconds"));
+    final String version = formParams.getFirst("Version");
+    final String awsIamSessionPolicy = formParams.getFirst("Policy");
+
+    return handleSTSRequest(
+        formParams.keySet(), action, roleArn, roleSessionName, 
durationSeconds, version, awsIamSessionPolicy);
   }
 
-  private Response handleSTSRequest(String action, String roleArn, String 
roleSessionName,
-      Integer durationSeconds, String version, String awsIamSessionPolicy) 
throws OS3Exception {
+  private Response handleSTSRequest(Set<String> paramNamesToValidate, String 
action, String roleArn,
+      String roleSessionName, Integer durationSeconds, String version, String 
awsIamSessionPolicy) throws OS3Exception {
     final String requestId = requestIdentifier.getRequestId();
     // NOTE: invalid, missing or unsupported actions are not added to the 
audit log
     try {
       if (action == null) {
         // Amazon STS has a different structure for the XML error response 
when the action is missing
-        return Response.status(BAD_REQUEST)
-            .entity("<UnknownOperationException/>")
-            .type(MediaType.APPLICATION_XML)
-            .build();
+        return unknownOperationExceptionResponse();
       }
 
       switch (action) {
       case ASSUME_ROLE_ACTION:
-        return handleAssumeRole(roleArn, roleSessionName, durationSeconds, 
awsIamSessionPolicy, version, requestId);
+        return handleAssumeRole(
+            paramNamesToValidate, roleArn, roleSessionName, durationSeconds, 
awsIamSessionPolicy, version, requestId);
       // These operations are not supported yet
       case GET_SESSION_TOKEN_ACTION:
       case ASSUME_ROLE_WITH_SAML_ACTION:
@@ -193,8 +215,8 @@ private Response handleSTSRequest(String action, String 
roleArn, String roleSess
     }
   }
 
-  private Response handleAssumeRole(String roleArn, String roleSessionName, 
Integer durationSeconds,
-      String awsIamSessionPolicy, String version, String requestId) throws 
OSTSException {
+  private Response handleAssumeRole(Set<String> paramNamesToValidate, String 
roleArn, String roleSessionName,
+      Integer durationSeconds, String awsIamSessionPolicy, String version, 
String requestId) throws OSTSException {
     final String action = "AssumeRole";
     final Map<String, String> auditParams = getAuditParameters();
     S3STSUtils.addAssumeRoleAuditParams(
@@ -211,6 +233,16 @@ private Response handleAssumeRole(String roleArn, String 
roleSessionName, Intege
       throw exception;
     }
 
+    final AssumeRoleParamValidationResult assumeRoleParamValidationResult = 
validateAssumeRoleParameters(
+        paramNamesToValidate);
+    if 
(!assumeRoleParamValidationResult.getNotImplementedOptionalParams().isEmpty()) {
+      final OSTSException exception = new 
OSTSException(STS_UNSUPPORTED_OPERATION).withMessage(
+          "AssumeRole optional parameter(s) not implemented: " +
+              String.join(", ", 
assumeRoleParamValidationResult.getNotImplementedOptionalParams()));
+      
getAuditLogger().logWriteFailure(buildAuditMessageForFailure(S3GAction.ASSUME_ROLE,
 auditParams, exception));
+      throw exception;
+    }
+
     final Set<String> validationErrors = new HashSet<>();
     int duration = durationSeconds == null ? 
S3STSUtils.DEFAULT_DURATION_SECONDS : durationSeconds;
     try {
@@ -242,6 +274,11 @@ private Response handleAssumeRole(String roleArn, String 
roleSessionName, Intege
       validationErrors.add(e.getMessage());
     }
 
+    if (!assumeRoleParamValidationResult.getUnsupportedParams().isEmpty()) {
+      validationErrors.add("Unsupported AssumeRole parameter(s): " + 
String.join(", ",
+          assumeRoleParamValidationResult.getUnsupportedParams()));
+    }
+
     final int numValidationErrors = validationErrors.size();
     if (numValidationErrors > 0) {
       //noinspection StringBufferReplaceableByString
@@ -303,6 +340,92 @@ private Response handleAssumeRole(String roleArn, String 
roleSessionName, Intege
     }
   }
 
+  private AssumeRoleParamValidationResult 
validateAssumeRoleParameters(Set<String> paramNamesToValidate) {
+    if (paramNamesToValidate == null || paramNamesToValidate.isEmpty()) {
+      return AssumeRoleParamValidationResult.empty();
+    }
+
+    final List<String> notImplementedOptionalParams = new ArrayList<>();
+    final List<String> unsupportedParams = new ArrayList<>();
+    for (String paramName : paramNamesToValidate) {
+      if (isAllowedAssumeRoleParameter(paramName)) {
+        continue;
+      }
+
+      if (isAwsValidButNotImplementedAssumeRoleParameter(paramName)) {
+        notImplementedOptionalParams.add(paramName);
+      } else {
+        unsupportedParams.add(paramName);
+      }
+    }
+
+    Collections.sort(notImplementedOptionalParams);
+    Collections.sort(unsupportedParams);
+    return new AssumeRoleParamValidationResult(notImplementedOptionalParams, 
unsupportedParams);
+  }
+
+  private static boolean isAllowedAssumeRoleParameter(String paramName) {
+    return ASSUME_ROLE_ALLOWED_PARAMS.contains(paramName) || 
Strings.CI.startsWith(paramName, SIGV4_PARAM_PREFIX);
+  }
+
+  private static Response unknownOperationExceptionResponse() {
+    return Response.status(BAD_REQUEST)
+        .entity("<UnknownOperationException/>")
+        .type(MediaType.APPLICATION_XML)
+        .build();
+  }
+
+  private static boolean isAwsValidButNotImplementedAssumeRoleParameter(String 
paramName) {
+    if (StringUtils.isBlank(paramName)) {
+      return false;
+    }
+    if (AWS_VALID_ASSUME_ROLE_OPTIONAL_PARAMS.contains(paramName)) {
+      return true;
+    }
+
+    return Strings.CI.startsWith(paramName, POLICY_ARNS_MEMBER_PREFIX)
+        || Strings.CI.startsWith(paramName, PROVIDED_CONTEXTS_MEMBER_PREFIX)
+        || Strings.CI.startsWith(paramName, TAGS_MEMBER_PREFIX)
+        || Strings.CI.startsWith(paramName, TRANSITIVE_TAG_KEYS_MEMBER_PREFIX);
+  }
+
+  private static Integer parseIntegerOrNull(String value) throws OSTSException 
{
+    if (StringUtils.isBlank(value)) {
+      return null;
+    }
+    try {
+      return Integer.parseInt(value);
+    } catch (NumberFormatException e) {
+      throw new OSTSException(STS_VALIDATION_ERROR)
+          .withMessage("1 validation error detected: Invalid Value: 
DurationSeconds must be a number");
+    }
+  }
+
+  private static final class AssumeRoleParamValidationResult {
+    private static final AssumeRoleParamValidationResult EMPTY = new 
AssumeRoleParamValidationResult(
+        Collections.emptyList(), Collections.emptyList());
+
+    private final List<String> notImplementedOptionalParams;
+    private final List<String> unsupportedParams;
+
+    private AssumeRoleParamValidationResult(List<String> 
notImplementedOptionalParams, List<String> unsupportedParams) {
+      this.notImplementedOptionalParams = notImplementedOptionalParams;
+      this.unsupportedParams = unsupportedParams;
+    }
+
+    private static AssumeRoleParamValidationResult empty() {
+      return EMPTY;
+    }
+
+    private List<String> getNotImplementedOptionalParams() {
+      return notImplementedOptionalParams;
+    }
+
+    private List<String> getUnsupportedParams() {
+      return unsupportedParams;
+    }
+  }
+
   private String generateAssumeRoleResponse(String assumedRoleUserArn, 
AssumeRoleResponseInfo responseInfo,
       String requestId) throws IOException {
     final String accessKeyId = responseInfo.getAccessKeyId();
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpointBase.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpointBase.java
index 027784b0edc..de52a29d6af 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpointBase.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpointBase.java
@@ -23,6 +23,8 @@
 import javax.inject.Inject;
 import javax.ws.rs.container.ContainerRequestContext;
 import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
 import org.apache.hadoop.ozone.audit.AuditAction;
 import org.apache.hadoop.ozone.audit.AuditEventStatus;
 import org.apache.hadoop.ozone.audit.AuditLogger;
@@ -133,4 +135,12 @@ public void setSignatureInfo(SignatureInfo signatureInfo) {
   protected Map<String, String> getAuditParameters() {
     return AuditUtils.getAuditParameters(context);
   }
+
+  protected MultivaluedMap<String, String> getQueryParameters() {
+    if (context == null || context.getUriInfo() == null) {
+      return new MultivaluedHashMap<>();
+    }
+    final MultivaluedMap<String, String> params = 
context.getUriInfo().getQueryParameters();
+    return params == null ? new MultivaluedHashMap<>() : params;
+  }
 }
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSEndpoint.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSEndpoint.java
index 059f54e0993..36adf2359c4 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSEndpoint.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSEndpoint.java
@@ -36,6 +36,7 @@
 import java.io.StringReader;
 import java.time.Instant;
 import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Form;
 import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
@@ -68,6 +69,8 @@ public class TestS3STSEndpoint {
   private S3STSEndpoint endpoint;
   private ObjectStore objectStore;
   private AuditLogger auditLogger;
+  private MultivaluedHashMap<String, String> queryParameters;
+  private Form formParameters;
   private static final String ROLE_ARN = 
"arn:aws:iam::123456789012:role/test-role";
   private static final String ROLE_SESSION_NAME = "test-session";
   private static final String ROLE_USER_ARN = 
"arn:aws:sts::123456789012:assumed-role/test-role/" + ROLE_SESSION_NAME;
@@ -87,7 +90,9 @@ public void setup() throws Exception {
     final UriInfo uriInfo = mock(UriInfo.class);
     when(context.getUriInfo()).thenReturn(uriInfo);
     when(uriInfo.getPathParameters()).thenReturn(new MultivaluedHashMap<>());
-    when(uriInfo.getQueryParameters()).thenReturn(new MultivaluedHashMap<>());
+    queryParameters = new MultivaluedHashMap<>();
+    when(uriInfo.getQueryParameters()).thenReturn(queryParameters);
+    formParameters = new Form();
 
     // Stub assumeRole to return deterministic credentials.
     objectStore = mock(ObjectStore.class);
@@ -117,6 +122,184 @@ public void setup() throws Exception {
     endpoint.setSignatureInfo(signatureInfo);
   }
 
+  @Test
+  public void testStsAssumeRoleRejectsUnsupportedParameterForGetMethod() 
throws Exception {
+    setAssumeRoleQueryParameters("PolicyArns.member.1", 
"arn:aws:iam::123456789012:policy/test-policy");
+
+    final OSTSException ex = assertThrows(
+        OSTSException.class, () -> endpoint.get("AssumeRole", ROLE_ARN, 
ROLE_SESSION_NAME, 3600, "2011-06-15", null));
+
+    assertEquals(501, ex.getHttpCode());
+    verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+    verify(auditLogger, never()).logWriteSuccess(any(AuditMessage.class));
+    verify(objectStore, never()).assumeRole(anyString(), anyString(), 
anyInt(), any(), anyString());
+
+    ex.setRequestId(REQUEST_ID);
+    assertStsErrorXml(
+        ex.toXml(), STS_NS, "Sender", "UnsupportedOperation",
+        "AssumeRole optional parameter(s) not implemented: 
PolicyArns.member.1");
+  }
+
+  @Test
+  public void testStsAssumeRoleRejectsUnsupportedParameterForPostMethod() 
throws Exception {
+    setBaseAssumeRoleFormParameters();
+    formParameters.param("ExternalId", "external-id");
+
+    final OSTSException ex = assertThrows(OSTSException.class, () -> 
endpoint.post(formParameters).close());
+
+    assertEquals(501, ex.getHttpCode());
+    verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+    verify(auditLogger, never()).logWriteSuccess(any(AuditMessage.class));
+    verify(objectStore, never()).assumeRole(anyString(), anyString(), 
anyInt(), any(), anyString());
+
+    ex.setRequestId(REQUEST_ID);
+    assertStsErrorXml(
+        ex.toXml(), STS_NS, "Sender", "UnsupportedOperation",
+        "AssumeRole optional parameter(s) not implemented: ExternalId");
+  }
+
+  @Test
+  public void testStsAssumeRoleAllowsSupportedParametersForGetMethod() {
+    setAssumeRoleQueryParameters(
+        "Action", "AssumeRole",
+        "RoleArn", ROLE_ARN,
+        "RoleSessionName", ROLE_SESSION_NAME,
+        "DurationSeconds", "3600",
+        "Version", "2011-06-15");
+
+    final Response response = endpoint.get("AssumeRole", ROLE_ARN, 
ROLE_SESSION_NAME, 3600, "2011-06-15", null);
+
+    assertEquals(200, response.getStatus());
+    verify(auditLogger).logWriteSuccess(any(AuditMessage.class));
+    verify(auditLogger, never()).logWriteFailure(any(AuditMessage.class));
+  }
+
+  @Test
+  public void testStsAssumeRoleAllowsSignatureParametersForGetMethod() {
+    setAssumeRoleQueryParameters(
+        "Action", "AssumeRole",
+        "RoleArn", ROLE_ARN,
+        "RoleSessionName", ROLE_SESSION_NAME,
+        "DurationSeconds", "3600",
+        "Version", "2011-06-15",
+        "X-Amz-Algorithm", "AWS4-HMAC-SHA256",
+        "X-Amz-Credential", "test-user/20260101/us-east-1/sts/aws4_request",
+        "X-Amz-Date", "20260101T000000Z",
+        "X-Amz-Expires", "3600",
+        "X-Amz-SignedHeaders", "host",
+        "X-Amz-Signature", 
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
+
+    final Response response = endpoint.get("AssumeRole", ROLE_ARN, 
ROLE_SESSION_NAME, 3600, "2011-06-15", null);
+
+    assertEquals(200, response.getStatus());
+    verify(auditLogger).logWriteSuccess(any(AuditMessage.class));
+    verify(auditLogger, never()).logWriteFailure(any(AuditMessage.class));
+  }
+
+  @Test
+  public void testStsAssumeRoleRejectsUnknownParameterForGetMethod() throws 
Exception {
+    setAssumeRoleQueryParameters("TotallyUnknownParam", "x");
+
+    final OSTSException ex = assertThrows(
+        OSTSException.class, () -> endpoint.get("AssumeRole", ROLE_ARN, 
ROLE_SESSION_NAME, 3600, "2011-06-15", null));
+
+    assertEquals(400, ex.getHttpCode());
+    verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+    verify(auditLogger, never()).logWriteSuccess(any(AuditMessage.class));
+    verify(objectStore, never()).assumeRole(anyString(), anyString(), 
anyInt(), any(), anyString());
+
+    ex.setRequestId(REQUEST_ID);
+    assertStsErrorXml(
+        ex.toXml(), STS_NS, "Sender", "ValidationError", "Unsupported 
AssumeRole parameter(s): TotallyUnknownParam");
+  }
+
+  @Test
+  public void testStsAssumeRoleRejectsUnknownParameterForPostMethod() throws 
Exception {
+    setBaseAssumeRoleFormParameters();
+    formParameters.param("TotallyUnknownParam", "y");
+
+    final OSTSException ex = assertThrows(OSTSException.class, () -> 
endpoint.post(formParameters).close());
+
+    assertEquals(400, ex.getHttpCode());
+    verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+    verify(auditLogger, never()).logWriteSuccess(any(AuditMessage.class));
+    verify(objectStore, never()).assumeRole(anyString(), anyString(), 
anyInt(), any(), anyString());
+
+    ex.setRequestId(REQUEST_ID);
+    assertStsErrorXml(
+        ex.toXml(), STS_NS, "Sender", "ValidationError",
+        "Unsupported AssumeRole parameter(s): TotallyUnknownParam");
+  }
+
+  @Test
+  public void testStsAssumeRoleRejectsBlankParameterNameForGetMethod() throws 
Exception {
+    setAssumeRoleQueryParameters("", "x");
+
+    final OSTSException ex = assertThrows(
+        OSTSException.class, () -> endpoint.get("AssumeRole", ROLE_ARN, 
ROLE_SESSION_NAME, 3600, "2011-06-15", null));
+
+    assertEquals(400, ex.getHttpCode());
+    verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+    verify(auditLogger, never()).logWriteSuccess(any(AuditMessage.class));
+    verify(objectStore, never()).assumeRole(anyString(), anyString(), 
anyInt(), any(), anyString());
+
+    ex.setRequestId(REQUEST_ID);
+    assertStsErrorXml(
+        ex.toXml(), STS_NS, "Sender", "ValidationError", "Unsupported 
AssumeRole parameter(s): ");
+  }
+
+  @Test
+  public void testStsAssumeRoleRejectsBlankParameterNameForPostMethod() throws 
Exception {
+    setBaseAssumeRoleFormParameters();
+    formParameters.param("", "x");
+
+    final OSTSException ex = assertThrows(OSTSException.class, () -> 
endpoint.post(formParameters).close());
+
+    assertEquals(400, ex.getHttpCode());
+    verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+    verify(auditLogger, never()).logWriteSuccess(any(AuditMessage.class));
+    verify(objectStore, never()).assumeRole(anyString(), anyString(), 
anyInt(), any(), anyString());
+
+    ex.setRequestId(REQUEST_ID);
+    assertStsErrorXml(
+        ex.toXml(), STS_NS, "Sender", "ValidationError", "Unsupported 
AssumeRole parameter(s): ");
+  }
+
+  @Test
+  public void 
testStsAssumeRoleIgnoresUnknownQueryStringParameterForPostMethod() {
+    queryParameters.add("foo", "bar");
+
+    // For POST requests, only body (form) parameters should be validated.
+    // Query string parameters should not affect validation results.
+    setBaseAssumeRoleFormParameters();
+    final Response response = endpoint.post(formParameters);
+    response.close();
+
+    assertEquals(200, response.getStatus());
+    verify(auditLogger).logWriteSuccess(any(AuditMessage.class));
+    verify(auditLogger, never()).logWriteFailure(any(AuditMessage.class));
+  }
+
+  @Test
+  public void 
testStsAssumeRoleRejectsUnsupportedSigningParametersForPostMethod() throws 
Exception {
+    setBaseAssumeRoleFormParameters();
+    formParameters.param("AWSAccessKeyId", "test-user");
+    formParameters.param("Signature", "signature");
+    formParameters.param("Expires", "3600");
+
+    final OSTSException ex = assertThrows(OSTSException.class, () -> 
endpoint.post(formParameters).close());
+
+    assertEquals(400, ex.getHttpCode());
+    verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+    verify(auditLogger, never()).logWriteSuccess(any(AuditMessage.class));
+    verify(objectStore, never()).assumeRole(anyString(), anyString(), 
anyInt(), any(), anyString());
+
+    ex.setRequestId(REQUEST_ID);
+    assertStsErrorXml(
+        ex.toXml(), STS_NS, "Sender", "ValidationError",
+        "Unsupported AssumeRole parameter(s): AWSAccessKeyId, Expires, 
Signature");
+  }
+
   @Test
   public void testStsAssumeRoleValidForGetMethod() throws Exception {
     final Response response = endpoint.get("AssumeRole", ROLE_ARN, 
ROLE_SESSION_NAME, 3600, "2011-06-15", null);
@@ -157,8 +340,9 @@ public void testStsAssumeRoleValidForGetMethod() throws 
Exception {
 
   @Test
   public void testStsAssumeRoleValidForPostMethod() throws Exception {
+    setBaseAssumeRoleFormParameters();
     //noinspection resource
-    final Response response = endpoint.post("AssumeRole", ROLE_ARN, 
ROLE_SESSION_NAME, 3600, "2011-06-15", null);
+    final Response response = endpoint.post(formParameters);
 
     assertEquals(200, response.getStatus());
     verify(auditLogger).logWriteSuccess(any(AuditMessage.class));
@@ -205,6 +389,22 @@ public void testStsNullAction() throws Exception {
     assertEquals("UnknownOperationException", root.getLocalName());
   }
 
+  @Test
+  public void testStsNullFormForPostMethod() throws Exception {
+    final Response response = endpoint.post(null);
+
+    assertEquals(400, response.getStatus());
+    verifyNoInteractions(auditLogger);
+    final String errorMessage = (String) response.getEntity();
+    assertEquals("<UnknownOperationException/>", errorMessage);
+
+    final Document doc = parseXml(errorMessage);
+    final Element root = doc.getDocumentElement();
+    assertEquals("UnknownOperationException", root.getLocalName());
+
+    response.close();
+  }
+
   @Test
   public void testStsUnsupportedActionWithVersionSupplied() throws Exception {
     final OSTSException ex = assertThrows(OSTSException.class, () ->
@@ -563,6 +763,29 @@ public void testStsMultipleValidationErrors() throws 
Exception {
         "'policy' failed to satisfy constraint: Member must have length less 
than or equal to 2048"));
   }
 
+  private void setAssumeRoleQueryParameters(String... nameValuePairs) {
+    queryParameters.clear();
+    for (int i = 0; i < nameValuePairs.length; i += 2) {
+      queryParameters.add(nameValuePairs[i], nameValuePairs[i + 1]);
+    }
+  }
+
+  private void setBaseAssumeRoleFormParameters() {
+    setAssumeRoleFormParameters(
+        "Action", "AssumeRole",
+        "RoleArn", ROLE_ARN,
+        "RoleSessionName", ROLE_SESSION_NAME,
+        "DurationSeconds", "3600",
+        "Version", "2011-06-15");
+  }
+
+  private void setAssumeRoleFormParameters(String... nameValuePairs) {
+    formParameters = new Form();
+    for (int i = 0; i < nameValuePairs.length; i += 2) {
+      formParameters.param(nameValuePairs[i], nameValuePairs[i + 1]);
+    }
+  }
+
   private static Document parseXml(String xml) throws Exception {
     final DocumentBuilderFactory documentBuilderFactory = 
DocumentBuilderFactory.newInstance();
     documentBuilderFactory.setNamespaceAware(true);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to