diff --git a/CHANGELOG.md b/CHANGELOG.md
index 60968bde225..4b0c3a8e410 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
### Added
- N/A
+## 1.3.2 - 2018-11-29
+### Added
+- Support for getting bucket statistics in the Object Storage service
+
+### Fixed
+- Storage service for copying volume backups across regions is now enabled
+- Objects can now be retrieved from Object Storage even if their content type is invalid
+
## 1.3.1 - 2018-11-15
### Added
- Support for VCN transit routing in the Networking service
@@ -44,15 +52,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
- Support for generating and downloading wallets in the Database service
- Support for creating a standalone backup from an on-premises database in the Database service
- Support for db version and additional connection strings in the Autonomous Transaction Processing and Autonomous Data Warehouse resources of the Database service
-- Support for copying volume backups across regions in the Block Storage service (please see Known issue)
+- Support for copying volume backups across regions in the Block Storage service
- Support for deleting compartments in the Identity service
- Support for reboot migration for virtual machines in the Compute service
- Support for Instance Pools and Instance Configurations in the Compute service
- `lengthPerUploadPart` provides a simpler way to control the size of parts when using Upload Manager
-### Known issue
-- Block Storage service for copying volume backups across regions is not yet enabled
-
### Breaking change
- The `dbDataSizeInMBs` field in the `com.oracle.bmc.database.model.Backup` and `com.oracle.bmc.database.model.BackupSummary` classes was renamed to `databaseSizeInGBs`, and its type was changed from `Integer` to `Double`
- Before
diff --git a/LICENSE.txt b/LICENSE.txt
index 63bc0c882eb..a115ecfb483 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,7 +1,10 @@
Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
-This software is dual-licensed to you under the Universal Permissive License (UPL) and Apache License 2.0. See below for license terms. You may choose either license, or both.
- ____________________________
+
+This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 or Apache License 2.0. See below for license terms. You may choose either license.
+
+____________________________
+
The Universal Permissive License (UPL), Version 1.0
Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
@@ -27,7 +30,7 @@ Apache License
Version 2.0, January 2004
-http://www.apache.org/licenses/
+http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
@@ -46,9 +49,9 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
You must give any other recipients of the Work or Derivative Works a copy of this License; and
You must cause any modified files to carry prominent notices stating that You changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
-If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
+If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
-You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
+You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
diff --git a/bmc-addons/bmc-apache-connector-provider/pom.xml b/bmc-addons/bmc-apache-connector-provider/pom.xml
index d3c93c8a8a2..66f4ac16d33 100644
--- a/bmc-addons/bmc-apache-connector-provider/pom.xml
+++ b/bmc-addons/bmc-apache-connector-provider/pom.xml
@@ -5,7 +5,7 @@
com.oracle.oci.sdkoci-java-sdk-addons
- 1.3.1
+ 1.3.2../pom.xml
@@ -37,7 +37,7 @@
com.oracle.oci.sdkoci-java-sdk-common
- 1.3.1
+ 1.3.2
diff --git a/bmc-addons/pom.xml b/bmc-addons/pom.xml
index 45e5ef0b793..a29a0303d33 100644
--- a/bmc-addons/pom.xml
+++ b/bmc-addons/pom.xml
@@ -5,7 +5,7 @@
com.oracle.oci.sdkoci-java-sdk
- 1.3.1
+ 1.3.2../pom.xml
diff --git a/bmc-audit/pom.xml b/bmc-audit/pom.xml
index a2b6c136ca6..84b0186cd8f 100644
--- a/bmc-audit/pom.xml
+++ b/bmc-audit/pom.xml
@@ -5,7 +5,7 @@
com.oracle.oci.sdkoci-java-sdk
- 1.3.1
+ 1.3.2../pom.xml
@@ -19,7 +19,7 @@
com.oracle.oci.sdkoci-java-sdk-common
- 1.3.1
+ 1.3.2
diff --git a/bmc-bom/pom.xml b/bmc-bom/pom.xml
index 72685590a73..d6b5d384514 100644
--- a/bmc-bom/pom.xml
+++ b/bmc-bom/pom.xml
@@ -5,7 +5,7 @@
com.oracle.oci.sdkoci-java-sdk
- 1.3.1
+ 1.3.2../pom.xmloci-java-sdk-bom
@@ -19,86 +19,86 @@
com.oracle.oci.sdkoci-java-sdk-common
- 1.3.1
+ 1.3.2falsecom.oracle.oci.sdkoci-java-sdk-audit
- 1.3.1
+ 1.3.2falsecom.oracle.oci.sdkoci-java-sdk-containerengine
- 1.3.1
+ 1.3.2falsecom.oracle.oci.sdkoci-java-sdk-core
- 1.3.1
+ 1.3.2falsecom.oracle.oci.sdkoci-java-sdk-database
- 1.3.1
+ 1.3.2falsecom.oracle.oci.sdkoci-java-sdk-dns
- 1.3.1
+ 1.3.2falsecom.oracle.oci.sdkoci-java-sdk-email
- 1.3.1
+ 1.3.2falsecom.oracle.oci.sdkoci-java-sdk-filestorage
- 1.3.1
+ 1.3.2falsecom.oracle.oci.sdkoci-java-sdk-identity
- 1.3.1
+ 1.3.2falsecom.oracle.oci.sdkoci-java-sdk-loadbalancer
- 1.3.1
+ 1.3.2falsecom.oracle.oci.sdkoci-java-sdk-objectstorage
- 1.3.1
+ 1.3.2falsecom.oracle.oci.sdkoci-java-sdk-resourcesearchfalse
- 1.3.1
+ 1.3.2com.oracle.oci.sdkfalseoci-java-sdk-addons-apache
- 1.3.1
+ 1.3.2com.oracle.oci.sdkoci-java-sdk-keymanagement
- 1.3.1
+ 1.3.2false
diff --git a/bmc-common/pom.xml b/bmc-common/pom.xml
index 32b26b6b020..1b1a6333313 100644
--- a/bmc-common/pom.xml
+++ b/bmc-common/pom.xml
@@ -5,7 +5,7 @@
com.oracle.oci.sdkoci-java-sdk
- 1.3.1
+ 1.3.2../pom.xml
diff --git a/bmc-common/src/main/java/com/oracle/bmc/Realm.java b/bmc-common/src/main/java/com/oracle/bmc/Realm.java
new file mode 100644
index 00000000000..d0e2cf8380f
--- /dev/null
+++ b/bmc-common/src/main/java/com/oracle/bmc/Realm.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ */
+package com.oracle.bmc;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Enumeration of all Identity realms.
+ *
+ * Accounts (tenancies) are per Realm.
+ */
+@RequiredArgsConstructor
+public enum Realm {
+ OC1("oc1");
+
+ @Getter
+ /**
+ * The id of the realm.
+ */
+ private final String realmId;
+}
diff --git a/bmc-common/src/main/java/com/oracle/bmc/Region.java b/bmc-common/src/main/java/com/oracle/bmc/Region.java
index b7522a2d480..a032b907895 100644
--- a/bmc-common/src/main/java/com/oracle/bmc/Region.java
+++ b/bmc-common/src/main/java/com/oracle/bmc/Region.java
@@ -3,17 +3,15 @@
*/
package com.oracle.bmc;
-import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
-import java.util.Properties;
import com.google.common.base.Optional;
+import com.oracle.bmc.internal.EndpointBuilder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
/**
* Enumeration of all of the known Regions that can be contacted.
@@ -23,44 +21,51 @@
@Slf4j
@RequiredArgsConstructor
public enum Region {
- US_PHOENIX_1("us-phoenix-1", "phx"),
- US_ASHBURN_1("us-ashburn-1", "iad"),
- EU_FRANKFURT_1("eu-frankfurt-1", "fra"),
- UK_LONDON_1("uk-london-1", "lhr");
+ // OC1
+ // regionCode for FRA shouldn't be needed, but left for backwards compat
+ EU_FRANKFURT_1("eu-frankfurt-1", "fra", Realm.OC1),
+ // regionCode for LHR shouldn't be needed, but left for backwards compat
+ UK_LONDON_1("uk-london-1", "lhr", Realm.OC1),
+ US_ASHBURN_1("us-ashburn-1", "iad", Realm.OC1),
+ US_PHOENIX_1("us-phoenix-1", "phx", Realm.OC1);
private static final Map> SERVICE_TO_REGION_ENDPOINTS =
new HashMap<>();
- private static final String DEFAULT_ENDPOINT_FORMAT;
-
- static {
- Properties properties = new Properties();
- try (InputStream propertyStream =
- Region.class
- .getClassLoader()
- .getResourceAsStream("com/oracle/bmc/sdk.properties")) {
- properties.load(propertyStream);
- } catch (Exception e) {
- throw new IllegalStateException("Failed to load required properties file", e);
- }
-
- DEFAULT_ENDPOINT_FORMAT = properties.getProperty("endpoint.defaultFormat");
- if (DEFAULT_ENDPOINT_FORMAT == null) {
- throw new IllegalStateException("Default endpoint format must be available");
- }
-
- LOG.info("Using default endpoint format of '{}'", DEFAULT_ENDPOINT_FORMAT);
- }
-
/**
* Get the region ID.
*/
@Getter private final String regionId;
+ /**
+ * The region code obtained from the 'region' field of instance metadata. This
+ * does not match the regionId in us-phoenix-1 and us-ashburn-1, but does in all
+ * other regions.
+ */
+ @Deprecated private final Optional regionCode;
+
+ /**
+ * Get the realm this region belongs to.
+ */
+ @Getter private final Realm realm;
+
+ private Region(String regionId, Realm realm) {
+ this(regionId, Optional.absent(), realm);
+ }
+
+ private Region(String regionId, String regionCode, Realm realm) {
+ this(regionId, Optional.of(regionCode), realm);
+ }
+
/**
* Get the region code.
+ * @Deprecated Do not use regionCode anymore. Use {@link #getRegionId()} to retrieve
+ * the canonical region ID.
*/
- @Getter private final String regionCode;
+ @Deprecated
+ public String getRegionCode() {
+ return regionCode.or(regionId);
+ }
/**
* Resolves a service name to its endpoint in the region, if available.
@@ -74,7 +79,7 @@ public synchronized Optional getEndpoint(Service service) {
if (!SERVICE_TO_REGION_ENDPOINTS.containsKey(service.getServiceName())) {
HashMap endpoints = new HashMap<>();
for (Region region : Region.values()) {
- String endpoint = formatDefaultRegionEndpoint(service, region.regionId);
+ String endpoint = formatDefaultRegionEndpoint(service, region);
endpoints.put(region, endpoint);
}
SERVICE_TO_REGION_ENDPOINTS.put(service.getServiceName(), endpoints);
@@ -95,18 +100,40 @@ public synchronized Optional getEndpoint(Service service) {
* a URL that follows the default format.
*
* @param service The service.
+ * @param region The region.
+ * @return The endpoint.
+ */
+ public static String formatDefaultRegionEndpoint(Service service, Region region) {
+ return EndpointBuilder.createEndpoint(service, region);
+ }
+
+ /**
+ * Creates a default endpoint URL for the given service in the given region.
+ *
+ *
+ * Note, the regionId is not validated against known regions, this just creates
+ * a URL that follows the default format.
+ *
+ * This method uses a realm of {@link Realm#OC1} if the region cannot be determined.
+ *
+ * @param service The service.
* @param regionId The region ID.
* @return The endpoint.
*/
public static String formatDefaultRegionEndpoint(Service service, String regionId) {
- if (StringUtils.isNotBlank(service.getServiceEndpointTemplate())) {
- return service.getServiceEndpointTemplate().replace("{region}", regionId);
+ // try to get a real region first
+ Optional maybeRegion = maybeFromRegionId(regionId);
+ if (maybeRegion.isPresent()) {
+ return formatDefaultRegionEndpoint(service, maybeRegion.get());
}
- return String.format(DEFAULT_ENDPOINT_FORMAT, service.getServiceEndpointPrefix(), regionId);
+
+ // else we need to fall back to OC1 SLD
+ LOG.debug("Unknown regionId '{}', will assume it's in Realm OC1", regionId);
+ return EndpointBuilder.createEndpoint(service, regionId, Realm.OC1);
}
/**
- * Returns the region enum from the public region ID. Throws
+ * Returns the region enum from the canonical public region ID. Throws
* IllegalArgumentException if the region ID is not known.
*
* @param regionId
@@ -114,12 +141,20 @@ public static String formatDefaultRegionEndpoint(Service service, String regionI
* @return The enum value.
*/
public static Region fromRegionId(String regionId) {
+ Optional maybeRegion = maybeFromRegionId(regionId);
+ if (maybeRegion.isPresent()) {
+ return maybeRegion.get();
+ }
+ throw new IllegalArgumentException("Unknown regionId: " + regionId);
+ }
+
+ private static Optional maybeFromRegionId(String regionId) {
for (Region region : Region.values()) {
if (region.regionId.equals(regionId)) {
- return region;
+ return Optional.of(region);
}
}
- throw new IllegalArgumentException("Unknown regionId: " + regionId);
+ return Optional.absent();
}
/**
@@ -129,10 +164,12 @@ public static Region fromRegionId(String regionId) {
* @param regionCode
* The region code.
* @return The enum value.
+ * @deprecated use {@link #fromRegionId(String)} and provide the canonical region ID.
*/
+ @Deprecated
public static Region fromRegionCode(String regionCode) {
for (Region region : Region.values()) {
- if (region.regionCode.compareToIgnoreCase(regionCode) == 0) {
+ if (region.getRegionCode().compareToIgnoreCase(regionCode) == 0) {
return region;
}
}
@@ -146,10 +183,12 @@ public static Region fromRegionCode(String regionCode) {
* @param regionCodeOrId
* The region code or id.
* @return The enum value.
+ * @deprecated use {@link #fromRegionId(String)} and provide the canonical region ID.
*/
+ @Deprecated
public static Region fromRegionCodeOrId(String regionCodeOrId) {
for (Region region : Region.values()) {
- if (region.regionCode.compareToIgnoreCase(regionCodeOrId) == 0
+ if (region.getRegionCode().compareToIgnoreCase(regionCodeOrId) == 0
|| region.regionId.compareToIgnoreCase(regionCodeOrId) == 0) {
return region;
}
diff --git a/bmc-common/src/main/java/com/oracle/bmc/Service.java b/bmc-common/src/main/java/com/oracle/bmc/Service.java
index 4e8be01dabd..849b8183dbc 100644
--- a/bmc-common/src/main/java/com/oracle/bmc/Service.java
+++ b/bmc-common/src/main/java/com/oracle/bmc/Service.java
@@ -12,7 +12,7 @@
public interface Service {
/**
- * The unique service name, ex "BLOCKSTORAGE"
+ * The unique service name, ex "BLOCKSTORAGE". Must not be null.
*/
String getServiceName();
@@ -24,7 +24,10 @@ public interface Service {
/**
* The service endpoint template that will be used, ex
- * "{region}.service.oci.oraclecloud.com"
+ * "{serviceEndpointPrefix}.{region}.service.oci.oraclecloud.com".
+ *
+ * This overrides the template used in {@link DefaultEndpointConfiguration}, but
+ * can still use the same variables.
*/
String getServiceEndpointTemplate();
}
diff --git a/bmc-common/src/main/java/com/oracle/bmc/Services.java b/bmc-common/src/main/java/com/oracle/bmc/Services.java
index 8a8bc4ee627..16c6ad81bcd 100644
--- a/bmc-common/src/main/java/com/oracle/bmc/Services.java
+++ b/bmc-common/src/main/java/com/oracle/bmc/Services.java
@@ -8,6 +8,8 @@
import lombok.Builder;
import lombok.Value;
+import lombok.extern.slf4j.Slf4j;
+
import org.apache.commons.lang3.Validate;
/**
@@ -16,8 +18,8 @@
* This serves to ensure conflicting definitions of services
* don't get created.
*/
+@Slf4j
public class Services {
-
private static final Map SERVICE_CACHE = new HashMap<>();
/**
@@ -54,6 +56,7 @@ private static synchronized Service create(
final String serviceEndpointPrefix,
final String serviceEndpointTemplate) {
Validate.notBlank(serviceName);
+
final Service newInstance =
new BasicService(serviceName, serviceEndpointPrefix, serviceEndpointTemplate);
if (SERVICE_CACHE.containsKey(serviceName)) {
@@ -68,6 +71,7 @@ private static synchronized Service create(
existing,
newInstance));
}
+ LOG.info("Registering new service: {}", newInstance);
SERVICE_CACHE.put(serviceName, newInstance);
return newInstance;
}
diff --git a/bmc-common/src/main/java/com/oracle/bmc/auth/InstancePrincipalsAuthenticationDetailsProvider.java b/bmc-common/src/main/java/com/oracle/bmc/auth/InstancePrincipalsAuthenticationDetailsProvider.java
index 353a1618d2a..bb9ba39dd5d 100644
--- a/bmc-common/src/main/java/com/oracle/bmc/auth/InstancePrincipalsAuthenticationDetailsProvider.java
+++ b/bmc-common/src/main/java/com/oracle/bmc/auth/InstancePrincipalsAuthenticationDetailsProvider.java
@@ -127,6 +127,8 @@ public InstancePrincipalsAuthenticationDetailsProvider build() {
String regionStr =
base.path("region").request(MediaType.TEXT_PLAIN).get(String.class);
+ // TODO: we should start using 'canonicalRegionName' instead of 'region' and call
+ // Region.fromRegionId, and fall back to 'region' only for backwards compat.
region = Region.fromRegionCodeOrId(regionStr);
Optional endpoint = region.getEndpoint(SERVICE);
diff --git a/bmc-common/src/main/java/com/oracle/bmc/auth/RegionProvider.java b/bmc-common/src/main/java/com/oracle/bmc/auth/RegionProvider.java
index 89dd8fd8dd0..726484bc102 100644
--- a/bmc-common/src/main/java/com/oracle/bmc/auth/RegionProvider.java
+++ b/bmc-common/src/main/java/com/oracle/bmc/auth/RegionProvider.java
@@ -7,6 +7,10 @@
/**
* A region provider has the API to return the region.
+ *
+ * An {@link AbstractAuthenticationDetailsProvider} can also implements this interface
+ * as a way to bootstrap a client during initialization using the Region returned
+ * by this interface.
*/
public interface RegionProvider {
/**
diff --git a/bmc-common/src/main/java/com/oracle/bmc/auth/StringPrivateKeySupplier.java b/bmc-common/src/main/java/com/oracle/bmc/auth/StringPrivateKeySupplier.java
index da631e70db3..0d3e8d7867f 100644
--- a/bmc-common/src/main/java/com/oracle/bmc/auth/StringPrivateKeySupplier.java
+++ b/bmc-common/src/main/java/com/oracle/bmc/auth/StringPrivateKeySupplier.java
@@ -3,13 +3,13 @@
*/
package com.oracle.bmc.auth;
-import com.google.common.base.Charsets;
import com.google.common.base.Supplier;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.apache.commons.io.IOUtils;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
/**
* Supplier for private key in String format
@@ -22,6 +22,6 @@ public class StringPrivateKeySupplier implements Supplier {
@Override
public InputStream get() {
- return IOUtils.toInputStream(privateKey, Charsets.UTF_8);
+ return IOUtils.toInputStream(privateKey, StandardCharsets.UTF_8);
}
}
diff --git a/bmc-common/src/main/java/com/oracle/bmc/auth/URLBasedX509CertificateSupplier.java b/bmc-common/src/main/java/com/oracle/bmc/auth/URLBasedX509CertificateSupplier.java
index 5203aad37ce..493bdb3b56b 100644
--- a/bmc-common/src/main/java/com/oracle/bmc/auth/URLBasedX509CertificateSupplier.java
+++ b/bmc-common/src/main/java/com/oracle/bmc/auth/URLBasedX509CertificateSupplier.java
@@ -3,25 +3,45 @@
*/
package com.oracle.bmc.auth;
-import com.oracle.bmc.http.signing.internal.PEMFileRSAPrivateKeySupplier;
-import com.oracle.bmc.util.StreamUtils;
-
-import javax.security.auth.Refreshable;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.util.concurrent.atomic.AtomicReference;
+import javax.security.auth.Refreshable;
+
+import org.apache.commons.io.IOUtils;
+
+import com.oracle.bmc.auth.internal.X509CertificateWithOriginalPem;
+import com.oracle.bmc.http.signing.internal.PEMFileRSAPrivateKeySupplier;
+
+import lombok.extern.slf4j.Slf4j;
+
/**
* {@link X509CertificateSupplier} implementation that reads both certificate and private key
* off of URL. This class also provides a way to manually refresh the certificate and
* private key at any point.
*/
+@Slf4j
public class URLBasedX509CertificateSupplier implements X509CertificateSupplier, Refreshable {
+ /**
+ * Provide a way for the application environment to disable the X509 workaround by setting
+ * a system property to "true". On the command line, this can be done using
+ * `-Doci.sdk.experimental.suppressX509Workaround=true`
+ */
+ private static final boolean EXPERIMENTAL_SUPPRESS_X509_WORKAROUND =
+ Boolean.getBoolean("oci.sdk.experimental.suppressX509Workaround");
+
+ static {
+ LOG.info("suppressX509Workaround flag set to {}", EXPERIMENTAL_SUPPRESS_X509_WORKAROUND);
+ }
+
/**
* The certificate and the private key of certificate.
*/
@@ -93,10 +113,18 @@ public X509Certificate getCertificate() {
*/
@Override
public void refresh() {
- X509Certificate certificate = readCertificate(certificateUrl);
+ String rawCertificate = readRawCertificate(certificateUrl);
+ X509Certificate certificate = readCertificate(rawCertificate);
RSAPrivateKey privateKey = readPrivateKey(privateKeyUrl, privateKeyPassphraseCharacters);
-
- this.certificateAndKeyPair.set(new CertificateAndPrivateKeyPair(certificate, privateKey));
+ if (EXPERIMENTAL_SUPPRESS_X509_WORKAROUND) {
+ this.certificateAndKeyPair.set(
+ new CertificateAndPrivateKeyPair(certificate, privateKey));
+ } else {
+ X509CertificateWithOriginalPem wrappedCertificate =
+ new X509CertificateWithOriginalPem(certificate, rawCertificate);
+ this.certificateAndKeyPair.set(
+ new CertificateAndPrivateKeyPair(wrappedCertificate, privateKey));
+ }
}
/**
@@ -113,18 +141,21 @@ public boolean isCurrent() {
* @param certificateUrl the certificate url
* @return the certificate
*/
- private static X509Certificate readCertificate(URL certificateUrl) {
- InputStream is = null;
+ private static X509Certificate readCertificate(String certificate) {
try {
- is = certificateUrl.openStream();
CertificateFactory factory = CertificateFactory.getInstance("X.509");
- return (X509Certificate) factory.generateCertificate(is);
+ return (X509Certificate)
+ factory.generateCertificate(new ByteArrayInputStream(certificate.getBytes()));
} catch (CertificateException e) {
throw new IllegalArgumentException("Invalid certificate.", e);
+ }
+ }
+
+ private static String readRawCertificate(URL certificateUrl) {
+ try (InputStream is = certificateUrl.openStream()) {
+ return IOUtils.toString(is, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new IllegalArgumentException("Open stream of certificate failed.", e);
- } finally {
- StreamUtils.closeQuietly(is);
}
}
diff --git a/bmc-common/src/main/java/com/oracle/bmc/auth/internal/AuthUtils.java b/bmc-common/src/main/java/com/oracle/bmc/auth/internal/AuthUtils.java
index c9b14b2b2d5..da2354d5ea2 100644
--- a/bmc-common/src/main/java/com/oracle/bmc/auth/internal/AuthUtils.java
+++ b/bmc-common/src/main/java/com/oracle/bmc/auth/internal/AuthUtils.java
@@ -41,7 +41,6 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class AuthUtils {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
-
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
/**
@@ -54,8 +53,9 @@ public class AuthUtils {
public static String getFingerPrint(X509Certificate certificate) {
Preconditions.checkNotNull(certificate);
try {
+ final byte[] encodedCertificate = getEncodedCertificate(certificate);
MessageDigest md = MessageDigest.getInstance("SHA-1");
- md.update(certificate.getEncoded());
+ md.update(encodedCertificate);
String fingerprint = getHex(md.digest());
return formatStringWithSeparator(fingerprint);
@@ -198,7 +198,26 @@ public static String base64EncodeNoChunking(RSAPublicKey publicKey) {
public static String base64EncodeNoChunking(X509Certificate certificate)
throws CertificateEncodingException {
return new String(
- Base64.encodeBase64(certificate.getEncoded(), false), StandardCharsets.UTF_8);
+ Base64.encodeBase64(getEncodedCertificate(certificate), false),
+ StandardCharsets.UTF_8);
+ }
+
+ private static byte[] getEncodedCertificate(X509Certificate certificate)
+ throws CertificateEncodingException {
+ if (certificate instanceof X509CertificateWithOriginalPem) {
+ return getEncodedCertificateFromPem(
+ ((X509CertificateWithOriginalPem) certificate).getPemEncodedCertificate());
+ } else {
+ return certificate.getEncoded();
+ }
+ }
+
+ private static byte[] getEncodedCertificateFromPem(String pemEncodedCertificate) {
+ // strip out header and footer
+ return Base64.decodeBase64(
+ pemEncodedCertificate
+ .replace("-----BEGIN CERTIFICATE-----", "")
+ .replace("-----END CERTIFICATE-----", ""));
}
/**
diff --git a/bmc-common/src/main/java/com/oracle/bmc/auth/internal/ForwardingX509Certificate.java b/bmc-common/src/main/java/com/oracle/bmc/auth/internal/ForwardingX509Certificate.java
new file mode 100644
index 00000000000..19f3616879f
--- /dev/null
+++ b/bmc-common/src/main/java/com/oracle/bmc/auth/internal/ForwardingX509Certificate.java
@@ -0,0 +1,213 @@
+/**
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ */
+package com.oracle.bmc.auth.internal;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * Simple forwarding X509Cerficate class. Overrides all abstract and non-abtract methods.
+ */
+public abstract class ForwardingX509Certificate extends X509Certificate {
+
+ protected abstract X509Certificate delegate();
+
+ // BEGIN OVERRIDE non-abstract method from X509Certificate.class
+
+ @Override
+ public X500Principal getIssuerX500Principal() {
+ return delegate().getIssuerX500Principal();
+ }
+
+ @Override
+ public X500Principal getSubjectX500Principal() {
+ return delegate().getSubjectX500Principal();
+ }
+
+ @Override
+ public List getExtendedKeyUsage() throws CertificateParsingException {
+ return delegate().getExtendedKeyUsage();
+ }
+
+ @Override
+ public Collection> getSubjectAlternativeNames() throws CertificateParsingException {
+ return delegate().getSubjectAlternativeNames();
+ }
+
+ @Override
+ public Collection> getIssuerAlternativeNames() throws CertificateParsingException {
+ return delegate().getIssuerAlternativeNames();
+ }
+
+ // END OVERRIDE non-abstract method from X509Certificate.class
+
+ // BEGIN OVERRIDE non-abstract method from Certificate.class
+
+ @Override
+ public boolean equals(Object other) {
+ return delegate().equals(other);
+ }
+
+ @Override
+ public int hashCode() {
+ return delegate().hashCode();
+ }
+
+ // END OVERRIDE non-abstract method from Certificate.class
+
+ // everything else below are abstract methods
+
+ @Override
+ public boolean hasUnsupportedCriticalExtension() {
+ return delegate().hasUnsupportedCriticalExtension();
+ }
+
+ @Override
+ public Set getCriticalExtensionOIDs() {
+ return delegate().getCriticalExtensionOIDs();
+ }
+
+ @Override
+ public Set getNonCriticalExtensionOIDs() {
+ return delegate().getNonCriticalExtensionOIDs();
+ }
+
+ @Override
+ public byte[] getExtensionValue(String oid) {
+ return delegate().getExtensionValue(oid);
+ }
+
+ @Override
+ public void checkValidity()
+ throws CertificateExpiredException, CertificateNotYetValidException {
+ delegate().checkValidity();
+ }
+
+ @Override
+ public void checkValidity(Date date)
+ throws CertificateExpiredException, CertificateNotYetValidException {
+ delegate().checkValidity(date);
+ }
+
+ @Override
+ public int getVersion() {
+ return delegate().getVersion();
+ }
+
+ @Override
+ public BigInteger getSerialNumber() {
+ return delegate().getSerialNumber();
+ }
+
+ @Override
+ public Principal getIssuerDN() {
+ return delegate().getIssuerDN();
+ }
+
+ @Override
+ public Principal getSubjectDN() {
+ return delegate().getSubjectDN();
+ }
+
+ @Override
+ public Date getNotBefore() {
+ return delegate().getNotBefore();
+ }
+
+ @Override
+ public Date getNotAfter() {
+ return delegate().getNotAfter();
+ }
+
+ @Override
+ public byte[] getTBSCertificate() throws CertificateEncodingException {
+ return delegate().getTBSCertificate();
+ }
+
+ @Override
+ public byte[] getSignature() {
+ return delegate().getSignature();
+ }
+
+ @Override
+ public String getSigAlgName() {
+ return delegate().getSigAlgName();
+ }
+
+ @Override
+ public String getSigAlgOID() {
+ return delegate().getSigAlgOID();
+ }
+
+ @Override
+ public byte[] getSigAlgParams() {
+ return delegate().getSigAlgParams();
+ }
+
+ @Override
+ public boolean[] getIssuerUniqueID() {
+ return delegate().getIssuerUniqueID();
+ }
+
+ @Override
+ public boolean[] getSubjectUniqueID() {
+ return delegate().getSubjectUniqueID();
+ }
+
+ @Override
+ public boolean[] getKeyUsage() {
+ return delegate().getKeyUsage();
+ }
+
+ @Override
+ public int getBasicConstraints() {
+ return delegate().getBasicConstraints();
+ }
+
+ @Override
+ public byte[] getEncoded() throws CertificateEncodingException {
+ return delegate().getEncoded();
+ }
+
+ @Override
+ public void verify(PublicKey key)
+ throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+ NoSuchProviderException, SignatureException {
+ delegate().verify(key);
+ }
+
+ @Override
+ public void verify(PublicKey key, String sigProvider)
+ throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+ NoSuchProviderException, SignatureException {
+ delegate().verify(key, sigProvider);
+ }
+
+ @Override
+ public String toString() {
+ return delegate().toString();
+ }
+
+ @Override
+ public PublicKey getPublicKey() {
+ return delegate().getPublicKey();
+ }
+}
diff --git a/bmc-common/src/main/java/com/oracle/bmc/auth/internal/X509CertificateWithOriginalPem.java b/bmc-common/src/main/java/com/oracle/bmc/auth/internal/X509CertificateWithOriginalPem.java
new file mode 100644
index 00000000000..7d721012e6c
--- /dev/null
+++ b/bmc-common/src/main/java/com/oracle/bmc/auth/internal/X509CertificateWithOriginalPem.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ */
+package com.oracle.bmc.auth.internal;
+
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * X509CertificateWithOriginalPem is specifically used so that we can keep track of
+ * the original PEM encoded certificate, along with the parsed X509Certificate that
+ * it creates.
+ *
+ * When BouncyCastle (standard or FIPs) is installed as a security provider, and is placed
+ * first in the list of security providers, the way it parses the PEM file into a X509Certificate
+ * causes the ordering of OU entries to be modified. When {@link Certificate#getEncoded()} is called,
+ * the encoded form no longer matches the original value that is in the PEM file.
+ *
+ * When using Instance Principals, we need to send back the original encoded form of the X509, along with
+ * it's fingerprint, so that Identity can verify it. If BouncyCastle is used, though, the certificate
+ * will look tampered with because the encoded form doesn't match what Identity expects. For this case
+ * specifically, we will attempt to get the encoded bytes from the original PEM file instead and pass
+ * them back as is, without parsing it to a X509Certificate.
+ */
+@RequiredArgsConstructor
+public class X509CertificateWithOriginalPem extends ForwardingX509Certificate {
+ private final X509Certificate delegate;
+ /**
+ * The original PEM encoded X509.
+ */
+ @Getter private final String pemEncodedCertificate;
+
+ @Override
+ protected X509Certificate delegate() {
+ return delegate;
+ }
+}
diff --git a/bmc-common/src/main/java/com/oracle/bmc/auth/internal/X509FederationClient.java b/bmc-common/src/main/java/com/oracle/bmc/auth/internal/X509FederationClient.java
index 9bde4d12771..dc13016a80f 100644
--- a/bmc-common/src/main/java/com/oracle/bmc/auth/internal/X509FederationClient.java
+++ b/bmc-common/src/main/java/com/oracle/bmc/auth/internal/X509FederationClient.java
@@ -183,20 +183,6 @@ private String refreshAndGetSecurityTokenInner(final boolean doFinalTokenValidit
}
}
- private static List removeHostHeader(List headers) {
- List copy = new ArrayList<>();
- for (String header : headers) {
- if (!header.equals(Constants.HOST)) {
- copy.add(header);
- }
- }
- return copy;
- }
-
- private static String keyIdForX509Request(String tenantId, X509Certificate certificate) {
- return String.format("%s/fed-x509/%s", tenantId, AuthUtils.getFingerPrint(certificate));
- }
-
/**
* Gets a security token from the federation server
* @return the security token, which is basically a JWT token string
diff --git a/bmc-common/src/main/java/com/oracle/bmc/http/internal/ResponseHelper.java b/bmc-common/src/main/java/com/oracle/bmc/http/internal/ResponseHelper.java
index 9b77f9e6219..0d9098770ca 100644
--- a/bmc-common/src/main/java/com/oracle/bmc/http/internal/ResponseHelper.java
+++ b/bmc-common/src/main/java/com/oracle/bmc/http/internal/ResponseHelper.java
@@ -3,12 +3,15 @@
*/
package com.oracle.bmc.http.internal;
+import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
@@ -175,23 +178,35 @@ public static T readEntity(@NonNull final Response response, Class entity
// handle the response
synchronized (response) {
if (response.getStatusInfo().getFamily().equals(Status.Family.SUCCESSFUL)) {
- if (entityType != InputStream.class) {
- // buffer entity so it can be reread during client parsing (ex, async requests reading
- // through both an AsyncHandler and through the returned Future)
- // NOTE: do not buffer InputStreams (namely object storage) as those might be very large
- response.bufferEntity();
+ if (entityType == InputStream.class) {
+ // If we want an InputStream, then we don't care about the content type.
+ // This will also allow us to process invalid content types like "text" (instead of "text/plain").
+ List