This is an automated email from the ASF dual-hosted git repository.
sammichen 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 bb2b540bd28 HDDS-14514. [STS] Revamp error handling in endpoint to
conform to AWS XML (#9674)
bb2b540bd28 is described below
commit bb2b540bd28a6f95de2ede4e8f8c2c208112755c
Author: fmorg-git <[email protected]>
AuthorDate: Wed Jan 28 06:17:27 2026 -0800
HDDS-14514. [STS] Revamp error handling in endpoint to conform to AWS XML
(#9674)
---
.../hadoop/ozone/s3/exception/OSTSException.java | 160 ++++++++++
.../ozone/s3/exception/OSTSExceptionMapper.java | 49 +++
.../org/apache/hadoop/ozone/s3sts/Application.java | 2 +
.../apache/hadoop/ozone/s3sts/S3STSEndpoint.java | 151 +++++----
.../apache/hadoop/ozone/s3sts/package-info.java | 11 +
.../ozone/s3/exception/TestOSTSExceptions.java | 102 ++++++
.../hadoop/ozone/s3sts/TestS3STSEndpoint.java | 346 +++++++++++++++++++--
7 files changed, 725 insertions(+), 96 deletions(-)
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OSTSException.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OSTSException.java
new file mode 100644
index 00000000000..57c870a7eb0
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OSTSException.java
@@ -0,0 +1,160 @@
+/*
+ * 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.hadoop.ozone.s3.exception;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class represents exceptions raised from Ozone STS service.
+ */
+public class OSTSException extends OS3Exception {
+ private static final Logger LOG =
LoggerFactory.getLogger(OSTSException.class);
+ private static final ObjectMapper MAPPER;
+ private static final String AWS_FAULT_NS =
"http://webservices.amazon.com/AWSFault/2005-15-09";
+ private static final String STS_NS =
"https://sts.amazonaws.com/doc/2011-06-15/";
+ private static final String INVALID_ACTION = "InvalidAction";
+
+ static {
+ MAPPER = new XmlMapper();
+ MAPPER.registerModule(new JaxbAnnotationModule());
+ MAPPER.enable(SerializationFeature.INDENT_OUTPUT);
+ }
+
+ private String type = "Sender";
+
+ public OSTSException(String codeVal, String messageVal, int httpCode) {
+ super(codeVal, messageVal, httpCode);
+ }
+
+ public OSTSException(String codeVal, String messageVal, int httpCode, String
typeVal) {
+ this(codeVal, messageVal, httpCode);
+ this.type = typeVal;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ @Override
+ public String toXml() {
+ try {
+ final ErrorResponse response = new ErrorResponse(this);
+ final String val = MAPPER.writeValueAsString(response);
+ LOG.debug("toXml val is {}", val);
+ return val; // STS error responses don't have prolog <?xml
version="1.0" encoding="UTF-8"?>
+ } catch (Exception ex) {
+ LOG.error("Exception occurred", ex);
+ // Fallback
+ final String namespace = INVALID_ACTION.equals(getCode()) ? AWS_FAULT_NS
: STS_NS;
+
+ // STS error responses don't have prolog <?xml version="1.0"
encoding="UTF-8"?>
+ final StringBuilder builder = new StringBuilder();
+ builder.append("<ErrorResponse
xmlns=\"").append(namespace).append("\">\n")
+ .append(" <Error>\n")
+ .append(" <Type>").append(getType()).append("</Type>\n")
+ .append(" <Code>").append(getCode()).append("</Code>\n")
+ .append("
<Message>").append(getErrorMessage()).append("</Message>\n")
+ .append(" </Error>\n")
+ .append("
<RequestId>").append(getRequestId()).append("</RequestId>\n")
+ .append("</ErrorResponse>");
+ return builder.toString();
+ }
+ }
+
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlRootElement(name = "ErrorResponse")
+ private static class ErrorResponse {
+
+ @XmlAttribute
+ private String xmlns;
+
+ @XmlElement(name = "Error")
+ private ErrorDetails error;
+
+ @XmlElement(name = "RequestId")
+ private String requestId;
+
+ ErrorResponse() {
+ }
+
+ ErrorResponse(OSTSException ex) {
+ this.xmlns = INVALID_ACTION.equals(ex.getCode()) ? AWS_FAULT_NS : STS_NS;
+ this.error = new ErrorDetails(ex.getType(), ex.getCode(),
ex.getErrorMessage());
+ this.requestId = ex.getRequestId();
+ }
+
+ public String getXmlns() {
+ return xmlns;
+ }
+
+ public ErrorDetails getError() {
+ return error;
+ }
+
+ public String getRequestId() {
+ return requestId;
+ }
+ }
+
+ @XmlAccessorType(XmlAccessType.FIELD)
+ private static class ErrorDetails {
+ @XmlElement(name = "Type")
+ private String type;
+
+ @XmlElement(name = "Code")
+ private String code;
+
+ @XmlElement(name = "Message")
+ private String message;
+
+ ErrorDetails() {
+ }
+
+ ErrorDetails(String type, String code, String message) {
+ this.type = type;
+ this.code = code;
+ this.message = message;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+ }
+}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OSTSExceptionMapper.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OSTSExceptionMapper.java
new file mode 100644
index 00000000000..bb564e0e061
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OSTSExceptionMapper.java
@@ -0,0 +1,49 @@
+/*
+ * 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.hadoop.ozone.s3.exception;
+
+import javax.inject.Inject;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+import org.apache.hadoop.ozone.s3.RequestIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class that represents various errors returned by the Ozone STS service.
+ */
+@Provider
+public class OSTSExceptionMapper implements ExceptionMapper<OSTSException> {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(OSTSExceptionMapper.class);
+
+ @Inject
+ private RequestIdentifier requestIdentifier;
+
+ @Override
+ public Response toResponse(OSTSException exception) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Returning exception. ex: {}", exception.toString());
+ }
+ exception.setRequestId(requestIdentifier.getRequestId());
+ return Response.status(exception.getHttpCode())
+ .entity(exception.toXml()).build();
+ }
+}
+
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/Application.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/Application.java
index 65081d5d47f..1605532db1c 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/Application.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/Application.java
@@ -17,6 +17,7 @@
package org.apache.hadoop.ozone.s3sts;
+import org.apache.hadoop.ozone.s3.exception.OSTSExceptionMapper;
import org.glassfish.jersey.server.ResourceConfig;
/**
@@ -26,5 +27,6 @@ public class Application extends ResourceConfig {
public Application() {
packages("org.apache.hadoop.ozone.s3sts");
register(org.apache.hadoop.ozone.s3.AuthorizationFilter.class);
+ register(OSTSExceptionMapper.class);
}
}
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 e33e9f80552..9d6b1b8d77f 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
@@ -17,6 +17,11 @@
package org.apache.hadoop.ozone.s3sts;
+import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
+import static javax.ws.rs.core.Response.Status.FORBIDDEN;
+import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
+import static javax.ws.rs.core.Response.Status.NOT_IMPLEMENTED;
+
import com.google.common.base.Strings;
import java.io.IOException;
import java.io.StringWriter;
@@ -35,8 +40,10 @@
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.apache.hadoop.ozone.s3.exception.OSTSException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -58,9 +65,7 @@ public class S3STSEndpoint extends S3STSEndpointBase {
private static final Logger LOG =
LoggerFactory.getLogger(S3STSEndpoint.class);
// STS API constants
- private static final String STS_ACTION_PARAM = "Action";
private static final String ASSUME_ROLE_ACTION = "AssumeRole";
- private static final String ROLE_ARN_PARAM = "RoleArn";
private static final String ROLE_DURATION_SECONDS_PARAM = "DurationSeconds";
private static final String GET_SESSION_TOKEN_ACTION = "GetSessionToken";
private static final String ASSUME_ROLE_WITH_SAML_ACTION =
"AssumeRoleWithSAML";
@@ -69,6 +74,8 @@ public class S3STSEndpoint extends S3STSEndpointBase {
private static final String DECODE_AUTHORIZATION_MESSAGE_ACTION =
"DecodeAuthorizationMessage";
private static final String GET_ACCESS_KEY_INFO_ACTION = "GetAccessKeyInfo";
+ private static final String EXPECTED_VERSION = "2011-06-15";
+
// Default token duration (in seconds) - AWS default is 3600 (1 hour)
// TODO - add these constants and also validations in a common place that
both endpoint and backend can use
private static final int DEFAULT_DURATION_SECONDS = 3600;
@@ -126,30 +133,19 @@ public Response post(
private Response handleSTSRequest(String action, String roleArn, String
roleSessionName,
Integer durationSeconds, String version, String awsIamSessionPolicy)
throws OS3Exception {
+ final String requestId = UUID.randomUUID().toString();
try {
if (action == null) {
- return Response.status(Response.Status.BAD_REQUEST)
- .entity("Missing required parameter: " + STS_ACTION_PARAM)
- .build();
- }
- int duration;
- try {
- duration = validateDuration(durationSeconds);
- } catch (IllegalArgumentException e) {
- return Response.status(Response.Status.BAD_REQUEST)
- .entity(e.getMessage())
- .build();
- }
-
- if (version == null || !version.equals("2011-06-15")) {
- return Response.status(Response.Status.BAD_REQUEST)
- .entity("Invalid or missing Version parameter. Supported version
is 2011-06-15.")
+ // 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();
}
switch (action) {
case ASSUME_ROLE_ACTION:
- return handleAssumeRole(roleArn, roleSessionName, duration,
awsIamSessionPolicy);
+ return handleAssumeRole(roleArn, roleSessionName, durationSeconds,
awsIamSessionPolicy, version, requestId);
// These operations are not supported yet
case GET_SESSION_TOKEN_ACTION:
case ASSUME_ROLE_WITH_SAML_ACTION:
@@ -157,25 +153,24 @@ private Response handleSTSRequest(String action, String
roleArn, String roleSess
case GET_CALLER_IDENTITY_ACTION:
case DECODE_AUTHORIZATION_MESSAGE_ACTION:
case GET_ACCESS_KEY_INFO_ACTION:
- return Response.status(Response.Status.NOT_IMPLEMENTED)
- .entity("Operation " + action + " is not supported yet.")
- .build();
+ throw new OSTSException(
+ "InvalidAction", "Operation " + action + " is not supported yet.",
NOT_IMPLEMENTED.getStatusCode());
default:
- return Response.status(Response.Status.BAD_REQUEST)
- .entity("Unsupported Action: " + action)
- .build();
+ throw new OSTSException(
+ "InvalidAction", "Could not find operation " + action + " for
version " +
+ (version == null ? "NO_VERSION_SPECIFIED. Expected version is: "
+ EXPECTED_VERSION : version),
+ BAD_REQUEST.getStatusCode());
}
- } catch (OS3Exception s3e) {
- // Handle known S3 exceptions
- LOG.error("S3 Error during STS request: {}", s3e.toXml());
- throw s3e;
+ } catch (OSTSException e) {
+ throw e;
} catch (Exception ex) {
LOG.error("Unexpected error during STS request", ex);
- return Response.serverError().build();
+ throw new OSTSException(
+ "InternalFailure", "An internal error has occurred.",
INTERNAL_SERVER_ERROR.getStatusCode(), "Receiver");
}
}
- private int validateDuration(Integer durationSeconds) throws
IllegalArgumentException, OS3Exception {
+ private int validateDuration(Integer durationSeconds) throws
IllegalArgumentException {
if (durationSeconds == null) {
return DEFAULT_DURATION_SECONDS;
}
@@ -189,53 +184,87 @@ private int validateDuration(Integer durationSeconds)
throws IllegalArgumentExce
return durationSeconds;
}
- private Response handleAssumeRole(String roleArn, String roleSessionName,
int duration, String awsIamSessionPolicy)
- throws IOException, OS3Exception {
- // Validate required parameters for AssumeRole. RoleArn is required
+ private Response handleAssumeRole(String roleArn, String roleSessionName,
Integer durationSeconds,
+ String awsIamSessionPolicy, String version, String requestId) throws
OSTSException {
+ // Validate parameters
+ final String action = "AssumeRole";
+ int duration;
+ try {
+ duration = validateDuration(durationSeconds);
+ } catch (IllegalArgumentException e) {
+ throw new OSTSException("ValidationError", e.getMessage(),
BAD_REQUEST.getStatusCode());
+ }
+
+ if (version == null || !version.equals(EXPECTED_VERSION)) {
+ throw new OSTSException(
+ "InvalidAction", "Could not find operation " + action + " for
version " +
+ (version == null ? "NO_VERSION_SPECIFIED. Expected version is: " +
EXPECTED_VERSION : version),
+ BAD_REQUEST.getStatusCode());
+ }
+
if (roleArn == null || roleArn.isEmpty()) {
- return Response.status(Response.Status.BAD_REQUEST)
- .entity("Missing required parameter: " + ROLE_ARN_PARAM)
- .build();
+ throw new OSTSException(
+ "ValidationError", "Value null at 'roleArn' failed to satisfy
constraint: Member must not be null",
+ BAD_REQUEST.getStatusCode());
}
if (roleSessionName == null || roleSessionName.isEmpty()) {
- return Response.status(Response.Status.BAD_REQUEST)
- .entity("Missing required parameter: RoleSessionName")
- .build();
+ throw new OSTSException(
+ "ValidationError", "Value null at 'roleSessionName' failed to
satisfy constraint: Member must not be null",
+ BAD_REQUEST.getStatusCode());
}
// Validate role session name format (AWS requirements)
if (!isValidRoleSessionName(roleSessionName)) {
- return Response.status(Response.Status.BAD_REQUEST)
- .entity("Invalid RoleSessionName: must be 2-64 characters long and "
+
- "contain only alphanumeric characters, +, =, ,, ., @, -")
- .build();
+ throw new OSTSException(
+ "ValidationError", "Invalid RoleSessionName: must be 2-64 characters
long and " +
+ "contain only alphanumeric characters, +, =, ,, ., @, -",
+ BAD_REQUEST.getStatusCode());
}
// Check Policy size if available
if (awsIamSessionPolicy != null && awsIamSessionPolicy.length() >
MAX_SESSION_POLICY_SIZE) {
- return Response.status(Response.Status.BAD_REQUEST)
- .entity("Policy length exceeded maximum allowed length of " +
MAX_SESSION_POLICY_SIZE)
- .build();
+ throw new OSTSException(
+ "ValidationError", "Value '" + awsIamSessionPolicy + "' at 'policy'
failed to satisfy constraint: Member " +
+ "must have length less than or equal to 2048",
BAD_REQUEST.getStatusCode());
}
final String assumedRoleUserArn;
try {
assumedRoleUserArn = toAssumedRoleUserArn(roleArn, roleSessionName);
} catch (IllegalArgumentException e) {
- return Response.status(Response.Status.BAD_REQUEST)
- .entity(e.getMessage())
- .build();
+ throw new OSTSException("ValidationError", e.getMessage(),
BAD_REQUEST.getStatusCode());
}
- final AssumeRoleResponseInfo responseInfo = getClient()
- .getObjectStore()
- .assumeRole(roleArn, roleSessionName, duration, awsIamSessionPolicy);
- // Generate AssumeRole response
- final String responseXml = generateAssumeRoleResponse(assumedRoleUserArn,
responseInfo);
- return Response.ok(responseXml)
- .header("Content-Type", "text/xml")
- .build();
+ try {
+ final AssumeRoleResponseInfo responseInfo = getClient()
+ .getObjectStore()
+ .assumeRole(roleArn, roleSessionName, duration, awsIamSessionPolicy);
+ // Generate AssumeRole response
+ final String responseXml =
generateAssumeRoleResponse(assumedRoleUserArn, responseInfo, requestId);
+ return Response.ok(responseXml)
+ .header("Content-Type", "text/xml")
+ .build();
+ } catch (IOException e) {
+ LOG.error("Error during AssumeRole processing", e);
+ if (e instanceof OMException) {
+ final OMException omException = (OMException) e;
+ if (omException.getResult() == OMException.ResultCodes.ACCESS_DENIED ||
+ omException.getResult() ==
OMException.ResultCodes.PERMISSION_DENIED ||
+ omException.getResult() == OMException.ResultCodes.TOKEN_EXPIRED) {
+ throw new OSTSException(
+ "AccessDenied", "User is not authorized to perform:
sts:AssumeRole on resource: " + roleArn,
+ FORBIDDEN.getStatusCode());
+ }
+ if (omException.getResult() == OMException.ResultCodes.INVALID_TOKEN) {
+ throw new OSTSException(
+ "InvalidClientTokenId", "The security token included in the
request is invalid.",
+ FORBIDDEN.getStatusCode());
+ }
+ }
+ throw new OSTSException("InternalFailure", "An internal error has
occurred.",
+ INTERNAL_SERVER_ERROR.getStatusCode(), "Receiver");
+ }
}
private boolean isValidRoleSessionName(String roleSessionName) {
@@ -247,8 +276,8 @@ private boolean isValidRoleSessionName(String
roleSessionName) {
return roleSessionName.matches("[a-zA-Z0-9+=,.@\\-]+");
}
- private String generateAssumeRoleResponse(String assumedRoleUserArn,
AssumeRoleResponseInfo responseInfo)
- throws IOException {
+ private String generateAssumeRoleResponse(String assumedRoleUserArn,
AssumeRoleResponseInfo responseInfo,
+ String requestId) throws IOException {
final String accessKeyId = responseInfo.getAccessKeyId();
final String secretAccessKey = responseInfo.getSecretAccessKey();
final String sessionToken = responseInfo.getSessionToken();
@@ -257,8 +286,6 @@ private String generateAssumeRoleResponse(String
assumedRoleUserArn, AssumeRoleR
final String expiration = DateTimeFormatter.ISO_INSTANT.format(
Instant.ofEpochSecond(responseInfo.getExpirationEpochSeconds()).atOffset(ZoneOffset.UTC).toInstant());
- final String requestId = UUID.randomUUID().toString();
-
try {
final S3AssumeRoleResponseXml response = new S3AssumeRoleResponseXml();
final S3AssumeRoleResponseXml.AssumeRoleResult result = new
S3AssumeRoleResponseXml.AssumeRoleResult();
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/package-info.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/package-info.java
index 76f77800182..3383580a5eb 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/package-info.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/package-info.java
@@ -18,4 +18,15 @@
/**
* This package contains the AWS STS (Security Token Service) compatible API
for S3 Gateway.
*/
+@XmlSchema(
+ namespace = "https://sts.amazonaws.com/doc/2011-06-15/",
+ elementFormDefault = XmlNsForm.QUALIFIED,
+ xmlns = {
+ @XmlNs(prefix = "", namespaceURI =
"https://sts.amazonaws.com/doc/2011-06-15/")
+ }
+)
package org.apache.hadoop.ozone.s3sts;
+
+import javax.xml.bind.annotation.XmlNs;
+import javax.xml.bind.annotation.XmlNsForm;
+import javax.xml.bind.annotation.XmlSchema;
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/exception/TestOSTSExceptions.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/exception/TestOSTSExceptions.java
new file mode 100644
index 00000000000..cd77979ec20
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/exception/TestOSTSExceptions.java
@@ -0,0 +1,102 @@
+/*
+ * 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.hadoop.ozone.s3.exception;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.StringReader;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.apache.hadoop.ozone.web.utils.OzoneUtils;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.InputSource;
+
+/**
+ * This class tests OSTSException class. It is named TestOSTSExceptions
instead of
+ * TestOSTSException to avoid findbugs rule about classes ending in *Exception
must
+ * extend Exception or Throwable.
+ */
+public class TestOSTSExceptions {
+
+ @Test
+ public void testOSTSException() throws Exception {
+ final OSTSException ex = new OSTSException("ValidationError", "1
validation error detected", 400);
+ final String requestId = OzoneUtils.getRequestID();
+ ex.setRequestId(requestId);
+ final String val = ex.toXml();
+
+ final Document doc = parseXml(val);
+ final Element root = doc.getDocumentElement();
+ assertEquals("ErrorResponse", root.getLocalName());
+ // Ensure the response uses the default namespace (no prefix like "ns2:")
+ assertEquals("ErrorResponse", root.getNodeName());
+ assertEquals("https://sts.amazonaws.com/doc/2011-06-15/",
root.getNamespaceURI());
+
+ assertEquals("Sender",
doc.getElementsByTagName("Type").item(0).getTextContent());
+ assertEquals("ValidationError",
doc.getElementsByTagName("Code").item(0).getTextContent());
+ assertEquals("1 validation error detected",
doc.getElementsByTagName("Message").item(0).getTextContent());
+ assertEquals(requestId,
doc.getElementsByTagName("RequestId").item(0).getTextContent());
+ }
+
+ @Test
+ public void testOSTSExceptionInvalidAction() throws Exception {
+ final OSTSException ex = new OSTSException("InvalidAction", "Could not
find operation", 400);
+ final String requestId = OzoneUtils.getRequestID();
+ ex.setRequestId(requestId);
+ final String val = ex.toXml();
+
+ final Document doc = parseXml(val);
+ final Element root = doc.getDocumentElement();
+ assertEquals("ErrorResponse", root.getLocalName());
+ assertEquals("http://webservices.amazon.com/AWSFault/2005-15-09",
root.getNamespaceURI());
+
+ assertEquals("Sender",
doc.getElementsByTagName("Type").item(0).getTextContent());
+ assertEquals("InvalidAction",
doc.getElementsByTagName("Code").item(0).getTextContent());
+ assertEquals("Could not find operation",
doc.getElementsByTagName("Message").item(0).getTextContent());
+ assertEquals(requestId,
doc.getElementsByTagName("RequestId").item(0).getTextContent());
+ }
+
+ @Test
+ public void testOSTSExceptionWithCustomType() throws Exception {
+ final OSTSException ex = new OSTSException("InternalFailure", "An internal
error has occurred.", 500, "Receiver");
+ final String requestId = OzoneUtils.getRequestID();
+ ex.setRequestId(requestId);
+ final String val = ex.toXml();
+
+ final Document doc = parseXml(val);
+ final Element root = doc.getDocumentElement();
+ assertEquals("ErrorResponse", root.getLocalName());
+ assertEquals("https://sts.amazonaws.com/doc/2011-06-15/",
root.getNamespaceURI());
+
+ assertEquals("Receiver",
doc.getElementsByTagName("Type").item(0).getTextContent());
+ assertEquals("InternalFailure",
doc.getElementsByTagName("Code").item(0).getTextContent());
+ assertEquals("An internal error has occurred.",
doc.getElementsByTagName("Message").item(0).getTextContent());
+ assertEquals(requestId,
doc.getElementsByTagName("RequestId").item(0).getTextContent());
+ }
+
+ private static Document parseXml(String xml) throws Exception {
+ assertNotNull(xml);
+ final DocumentBuilderFactory documentBuilderFactory =
DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ final DocumentBuilder documentBuilder =
documentBuilderFactory.newDocumentBuilder();
+ return documentBuilder.parse(new InputSource(new StringReader(xml)));
+ }
+}
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 a78c2c394e5..aefb525448b 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
@@ -20,14 +20,17 @@
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_S3_ADMINISTRATORS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import java.io.IOException;
import java.io.StringReader;
import java.time.Instant;
import javax.ws.rs.container.ContainerRequestContext;
@@ -39,11 +42,14 @@
import org.apache.hadoop.ozone.client.ObjectStore;
import org.apache.hadoop.ozone.client.OzoneClient;
import org.apache.hadoop.ozone.client.OzoneClientStub;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
import org.apache.hadoop.ozone.s3.OzoneConfigurationHolder;
+import org.apache.hadoop.ozone.s3.exception.OSTSException;
import org.apache.hadoop.ozone.s3.signature.SignatureInfo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -54,9 +60,12 @@
*/
public class TestS3STSEndpoint {
private S3STSEndpoint endpoint;
+ private ObjectStore objectStore;
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;
+ private static final String STS_NS =
"https://sts.amazonaws.com/doc/2011-06-15/";
+ private static final String AWS_FAULT_NS =
"http://webservices.amazon.com/AWSFault/2005-15-09";
@Mock
private ContainerRequestContext context;
@@ -69,7 +78,7 @@ public void setup() throws Exception {
OzoneClient clientStub = spy(new OzoneClientStub());
// Stub assumeRole to return deterministic credentials.
- ObjectStore objectStore = mock(ObjectStore.class);
+ objectStore = mock(ObjectStore.class);
when(objectStore.assumeRole(anyString(), anyString(), anyInt(), any()))
.thenReturn(new AssumeRoleResponseInfo(
"ASIA1234567890123456",
@@ -91,7 +100,7 @@ public void setup() throws Exception {
}
@Test
- public void testStsAssumeRole() throws Exception {
+ public void testStsAssumeRoleValidForGetMethod() throws Exception {
Response response = endpoint.get(
"AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, 3600, "2011-06-15", null);
@@ -101,13 +110,18 @@ public void testStsAssumeRole() throws Exception {
assertNotNull(responseXml);
// Parse response XML and verify values
- final DocumentBuilderFactory documentBuilderFactory =
DocumentBuilderFactory.newInstance();
- documentBuilderFactory.setNamespaceAware(true);
- final DocumentBuilder documentBuilder =
documentBuilderFactory.newDocumentBuilder();
- final Document doc = documentBuilder.parse(new InputSource(new
StringReader(responseXml)));
+ final Document doc = parseXml(responseXml);
final Element root = doc.getDocumentElement();
assertEquals("AssumeRoleResponse", root.getLocalName());
+ assertEquals(STS_NS, root.getNamespaceURI());
+ // Ensure the response uses the default namespace (no prefix like "ns2:")
+ assertEquals("AssumeRoleResponse", root.getNodeName());
+
+ // Verify some key elements are present in the STS namespace
+ assertNotNull(doc.getElementsByTagNameNS(STS_NS,
"AssumeRoleResult").item(0));
+ assertNotNull(doc.getElementsByTagNameNS(STS_NS, "Credentials").item(0));
+ assertNotNull(doc.getElementsByTagNameNS(STS_NS, "AccessKeyId").item(0));
final String accessKeyId =
doc.getElementsByTagName("AccessKeyId").item(0).getTextContent();
assertEquals("ASIA1234567890123456", accessKeyId);
@@ -123,56 +137,320 @@ public void testStsAssumeRole() throws Exception {
}
@Test
- public void testStsInvalidDuration() throws Exception {
- Response response = endpoint.get(
- "AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, -1, "2011-06-15", null);
+ public void testStsAssumeRoleValidForPostMethod() throws Exception {
+ final Response response = endpoint.post("AssumeRole", ROLE_ARN,
ROLE_SESSION_NAME, 3600, "2011-06-15", null);
- assertEquals(400, response.getStatus());
- String errorMessage = (String) response.getEntity();
- assertTrue(errorMessage.contains("Invalid Value: DurationSeconds"));
+ assertEquals(200, response.getStatus());
+ final String responseXml = (String) response.getEntity();
+ assertNotNull(responseXml);
+
+ final Document doc = parseXml(responseXml);
+ final Element root = doc.getDocumentElement();
+ assertEquals("AssumeRoleResponse", root.getLocalName());
+ assertEquals(STS_NS, root.getNamespaceURI());
+ // Ensure the response uses the default namespace (no prefix like "ns2:")
+ assertEquals("AssumeRoleResponse", root.getNodeName());
+
+ // Verify some key elements are present in the STS namespace
+ assertNotNull(doc.getElementsByTagNameNS(STS_NS,
"AssumeRoleResult").item(0));
+ assertNotNull(doc.getElementsByTagNameNS(STS_NS, "Credentials").item(0));
+ assertNotNull(doc.getElementsByTagNameNS(STS_NS, "AccessKeyId").item(0));
+
+ final String accessKeyId =
doc.getElementsByTagName("AccessKeyId").item(0).getTextContent();
+ assertEquals("ASIA1234567890123456", accessKeyId);
+
+ final String secretAccessKey =
doc.getElementsByTagName("SecretAccessKey").item(0).getTextContent();
+ assertEquals("mySecretAccessKey", secretAccessKey);
+
+ final String sessionToken =
doc.getElementsByTagName("SessionToken").item(0).getTextContent();
+ assertEquals("session-token", sessionToken);
+
+ final String arn =
doc.getElementsByTagName("Arn").item(0).getTextContent();
+ assertEquals(ROLE_USER_ARN, arn);
}
@Test
- public void testStsUnsupportedAction() throws Exception {
- Response response = endpoint.get(
- "UnsupportedAction", ROLE_ARN, ROLE_SESSION_NAME, 3600, "2011-06-15",
null);
+ public void testStsNullAction() throws Exception {
+ final Response response = endpoint.get(null, ROLE_ARN, ROLE_SESSION_NAME,
3600, "2011-06-15", null);
assertEquals(400, response.getStatus());
- String errorMessage = (String) response.getEntity();
- assertTrue(errorMessage.contains("Unsupported Action"));
+ final String errorMessage = (String) response.getEntity();
+ assertEquals("<UnknownOperationException/>", errorMessage);
+
+ final Document doc = parseXml(errorMessage);
+ final Element root = doc.getDocumentElement();
+ assertEquals("UnknownOperationException", root.getLocalName());
}
@Test
- public void testStsInvalidVersion() throws Exception {
- Response response = endpoint.get(
- "AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, 3600, "2000-01-01", null);
+ public void testStsUnsupportedActionWithVersionSupplied() throws Exception {
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("UnsupportedAction", ROLE_ARN, ROLE_SESSION_NAME, 3600,
"2011-06-15", null));
- assertEquals(400, response.getStatus());
- String errorMessage = (String) response.getEntity();
- assertTrue(errorMessage.contains("Invalid or missing Version parameter.
Supported version is 2011-06-15."));
+ assertEquals(400, ex.getHttpCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), AWS_FAULT_NS, "Sender", "InvalidAction",
+ "Could not find operation UnsupportedAction for version 2011-06-15");
+ }
+
+ @Test
+ public void testStsUnsupportedActionWithVersionNotSupplied() throws
Exception {
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("UnsupportedAction", ROLE_ARN, ROLE_SESSION_NAME, 3600,
null, null));
+
+ assertEquals(400, ex.getHttpCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), AWS_FAULT_NS, "Sender", "InvalidAction",
+ "Could not find operation UnsupportedAction for version
NO_VERSION_SPECIFIED");
+ }
+
+ @Test
+ public void testStsAssumeRoleWithInvalidVersion() throws Exception {
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, 3600,
"2000-01-01", null));
+
+ assertEquals(400, ex.getHttpCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), AWS_FAULT_NS, "Sender", "InvalidAction",
+ "Could not find operation AssumeRole for version 2000-01-01");
+ }
+
+ @Test
+ public void testStsInvalidDuration() throws Exception {
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, -1,
"2011-06-15", null));
+
+ assertEquals(400, ex.getHttpCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), STS_NS, "Sender", "ValidationError",
"Invalid Value: DurationSeconds");
+ }
+
+ @Test
+ public void testStsNullDurationUsesDefault3600() throws Exception {
+ final Response response = endpoint.get(
+ "AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, null, "2011-06-15", null);
+ assertEquals(200, response.getStatus());
+
+ final ArgumentCaptor<Integer> durationCaptor =
ArgumentCaptor.forClass(Integer.class);
+ verify(objectStore).assumeRole(anyString(), anyString(),
durationCaptor.capture(), any());
+ assertEquals(3600, durationCaptor.getValue());
}
@Test
public void testStsPolicyTooLarge() throws Exception {
final String tooLargePolicy =
RandomStringUtils.insecure().nextAlphanumeric(2049);
- final Response response = endpoint.get(
- "AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, 3600, "2011-06-15",
tooLargePolicy);
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, 3600,
"2011-06-15", tooLargePolicy));
- assertEquals(400, response.getStatus());
- final String errorMessage = (String) response.getEntity();
- assertTrue(errorMessage.contains("Policy length exceeded maximum allowed
length of 2048"));
+ assertEquals(400, ex.getHttpCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), STS_NS, "Sender", "ValidationError",
+ "Value '" + tooLargePolicy + "' at 'policy' failed to satisfy
constraint: Member " +
+ "must have length less than or equal to 2048");
}
@Test
public void testStsInvalidRoleArn() throws Exception {
final String invalidRoleArn =
"arn:awsNotValid::123456789012:role/test-role";
- final Response response = endpoint.get(
- "AssumeRole", invalidRoleArn, ROLE_SESSION_NAME, 3600, "2011-06-15",
null);
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("AssumeRole", invalidRoleArn, ROLE_SESSION_NAME, 3600,
"2011-06-15", null));
- assertEquals(400, response.getStatus());
- final String errorMessage = (String) response.getEntity();
- assertTrue(
- errorMessage.contains("Invalid RoleArn: must be in the format
arn:aws:iam::<account-id>:role/<role-name>"));
+ assertEquals(400, ex.getHttpCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), STS_NS, "Sender", "ValidationError",
+ "Invalid RoleArn: must be in the format
arn:aws:iam::<account-id>:role/<role-name>");
+ }
+
+ @Test
+ public void testStsMissingRoleArn() throws Exception {
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("AssumeRole", null, ROLE_SESSION_NAME, 3600,
"2011-06-15", null));
+
+ assertEquals(400, ex.getHttpCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), STS_NS, "Sender", "ValidationError", "Value
null at 'roleArn'");
+ }
+
+ @Test
+ public void testStsInvalidRoleArnMissingRoleName() throws Exception {
+ final String invalidRoleArn = "arn:aws:iam::123456789012:role/";
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("AssumeRole", invalidRoleArn, ROLE_SESSION_NAME, 3600,
"2011-06-15", null));
+
+ assertEquals(400, ex.getHttpCode());
+ assertEquals("ValidationError", ex.getCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), STS_NS, "Sender", "ValidationError",
"Invalid RoleArn: must be in the format");
+ }
+
+ @Test
+ public void testStsInvalidRoleArnMissingAccountId() throws Exception {
+ final String invalidRoleArn = "arn:aws:iam:::role/test-role";
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("AssumeRole", invalidRoleArn, ROLE_SESSION_NAME, 3600,
"2011-06-15", null));
+
+ assertEquals(400, ex.getHttpCode());
+ assertEquals("ValidationError", ex.getCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), STS_NS, "Sender", "ValidationError",
"Invalid RoleArn: must be in the format"
+ );
+ }
+
+ @Test
+ public void testStsWhenActionNotImplemented() throws Exception {
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("GetSessionToken", ROLE_ARN, ROLE_SESSION_NAME, 3600,
"2011-06-15", null));
+
+ assertEquals(501, ex.getHttpCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), AWS_FAULT_NS, "Sender", "InvalidAction",
+ "Operation GetSessionToken is not supported yet.");
+ }
+
+ @Test
+ public void testStsMissingRoleSessionName() throws Exception {
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("AssumeRole", ROLE_ARN, null, 3600, "2011-06-15", null));
+
+ assertEquals(400, ex.getHttpCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), STS_NS, "Sender", "ValidationError", "Value
null at 'roleSessionName'");
+ }
+
+ @Test
+ public void testStsInvalidRoleSessionNameWithInvalidCharacter() throws
Exception {
+ final String invalidSession = "test/session";
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("AssumeRole", ROLE_ARN, invalidSession, 3600,
"2011-06-15", null));
+
+ assertEquals(400, ex.getHttpCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), STS_NS, "Sender", "ValidationError",
"Invalid RoleSessionName");
+ }
+
+ @Test
+ public void testStsInvalidRoleSessionNameTooShort() throws Exception {
+ final String invalidSession = "a";
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("AssumeRole", ROLE_ARN, invalidSession, 3600,
"2011-06-15", null));
+
+ assertEquals(400, ex.getHttpCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), STS_NS, "Sender", "ValidationError",
"Invalid RoleSessionName");
+ }
+
+ @Test
+ public void testStsInvalidRoleArnResourceType() throws Exception {
+ // Resource type must be role, not user
+ final String invalidRoleArn = "arn:aws:iam::123456789012:user/test-user";
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("AssumeRole", invalidRoleArn, ROLE_SESSION_NAME, 3600,
"2011-06-15", null));
+
+ assertEquals(400, ex.getHttpCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), STS_NS, "Sender", "ValidationError",
"Invalid RoleArn: must be in the format");
+ }
+
+ @Test
+ public void testStsInternalFailureWhenBackendThrows() throws Exception {
+ when(objectStore.assumeRole(anyString(), anyString(), anyInt(), any()))
+ .thenThrow(new RuntimeException("some unexpected error"));
+
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, 3600,
"2011-06-15", null));
+
+ assertEquals(500, ex.getHttpCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), STS_NS, "Receiver", "InternalFailure", "An
internal error has occurred.");
+ }
+
+ @Test
+ public void testStsAccessDenied() throws Exception {
+ when(objectStore.assumeRole(anyString(), anyString(), anyInt(), any()))
+ .thenThrow(new OMException("Permission denied",
OMException.ResultCodes.ACCESS_DENIED));
+
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, 3600,
"2011-06-15", null));
+
+ assertEquals(403, ex.getHttpCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), STS_NS, "Sender", "AccessDenied",
+ "User is not authorized to perform: sts:AssumeRole on resource: " +
ROLE_ARN);
+ }
+
+ @Test
+ public void testStsIOExceptionWrappedAsInternalFailure() throws Exception {
+ when(objectStore.assumeRole(anyString(), anyString(), anyInt(), any()))
+ .thenThrow(new IOException("An IO error occurred"));
+
+ final OSTSException ex = assertThrows(OSTSException.class, () ->
+ endpoint.get("AssumeRole", ROLE_ARN, ROLE_SESSION_NAME, 3600,
"2011-06-15", null));
+
+ assertEquals(500, ex.getHttpCode());
+
+ final String requestId = "test-request-id";
+ ex.setRequestId(requestId);
+ assertStsErrorXml(ex.toXml(), STS_NS, "Receiver", "InternalFailure", "An
internal error has occurred.");
+ }
+
+ private static Document parseXml(String xml) throws Exception {
+ final DocumentBuilderFactory documentBuilderFactory =
DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ final DocumentBuilder documentBuilder =
documentBuilderFactory.newDocumentBuilder();
+ return documentBuilder.parse(new InputSource(new StringReader(xml)));
+ }
+
+ private static void assertStsErrorXml(String xml, String expectedNamespace,
String expectedType, String expectedCode,
+ String expectedMessageContains) throws Exception {
+ final Document doc = parseXml(xml);
+ final Element root = doc.getDocumentElement();
+ assertEquals("ErrorResponse", root.getLocalName());
+ assertEquals(expectedNamespace, root.getNamespaceURI());
+
+ final String type =
doc.getElementsByTagName("Type").item(0).getTextContent();
+ assertEquals(expectedType, type);
+
+ final String code =
doc.getElementsByTagName("Code").item(0).getTextContent();
+ assertEquals(expectedCode, code);
+
+ final String message =
doc.getElementsByTagName("Message").item(0).getTextContent();
+ assertNotNull(message);
+ assertTrue(message.contains(expectedMessageContains), "Expected message to
contain: " + expectedMessageContains);
+
+ final String requestId =
doc.getElementsByTagName("RequestId").item(0).getTextContent();
+ assertEquals("test-request-id", requestId);
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]