diff --git a/providers/dynect/src/main/java/org/jclouds/dynect/v3/domain/rdata/SPFData.java b/providers/dynect/src/main/java/org/jclouds/dynect/v3/domain/rdata/SPFData.java new file mode 100644 index 0000000000..37388dd0be --- /dev/null +++ b/providers/dynect/src/main/java/org/jclouds/dynect/v3/domain/rdata/SPFData.java @@ -0,0 +1,53 @@ +package org.jclouds.dynect.v3.domain.rdata; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.beans.ConstructorProperties; +import java.util.Map; + +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.ImmutableMap; + +/** + * Corresponds to the binary representation of the {@code SPF} (Sender Policy + * Framework) RData + * + *

Example

+ * + *
+ * import static denominator.model.rdata.SPFData.spf;
+ * ...
+ * SPFData rdata = spf("v=spf1 +mx a:colo.example.com/28 -all");
+ * 
+ * + * @see RFC 4408 + */ +public class SPFData extends ForwardingMap { + + public static SPFData create(String txtdata) { + return new SPFData(txtdata); + } + + private final String txtdata; + + @ConstructorProperties("txtdata") + private SPFData(String txtdata) { + this.txtdata = checkNotNull(txtdata, "txtdata"); + this.delegate = ImmutableMap. of("txtdata", txtdata); + } + + /** + * One or more character-strings. + */ + public String getTxtdata() { + return txtdata; + } + + // transient to avoid serializing by default, for example in json + private final transient ImmutableMap delegate; + + @Override + protected Map delegate() { + return delegate; + } +} diff --git a/providers/dynect/src/main/java/org/jclouds/dynect/v3/domain/rdata/SSHFPData.java b/providers/dynect/src/main/java/org/jclouds/dynect/v3/domain/rdata/SSHFPData.java new file mode 100644 index 0000000000..abc051f5f9 --- /dev/null +++ b/providers/dynect/src/main/java/org/jclouds/dynect/v3/domain/rdata/SSHFPData.java @@ -0,0 +1,133 @@ +package org.jclouds.dynect.v3.domain.rdata; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.beans.ConstructorProperties; +import java.util.Map; + +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.ImmutableMap; + +/** + * Corresponds to the binary representation of the {@code SSHFP} (SSH + * Fingerprint) RData + * + *

Example

+ * + *
+ * SSHFPData rdata = SSHFPData.builder().algorithm(2).fptype(1).fingerprint("123456789abcdef67890123456789abcdef67890")
+ *       .build();
+ * // or shortcut
+ * SSHFPData rdata = SSHFPData.createDSA("123456789abcdef67890123456789abcdef67890");
+ * 
+ * + * @see RFC 4255 + */ +public class SSHFPData extends ForwardingMap { + + /** + * @param fingerprint + * {@code DSA} {@code SHA-1} fingerprint + */ + public static SSHFPData createDSA(String fingerprint) { + return builder().algorithm(2).fptype(1).fingerprint(fingerprint).build(); + } + + /** + * @param fingerprint + * {@code RSA} {@code SHA-1} fingerprint + */ + public static SSHFPData createRSA(String fingerprint) { + return builder().algorithm(1).fptype(1).fingerprint(fingerprint).build(); + } + + private final int algorithm; + private final int fptype; + private final String fingerprint; + + @ConstructorProperties({ "algorithm", "fptype", "fingerprint" }) + private SSHFPData(int algorithm, int fptype, String fingerprint) { + checkArgument(algorithm >= 0, "algorithm of %s must be unsigned", fingerprint); + this.algorithm = algorithm; + checkArgument(fptype >= 0, "fptype of %s must be unsigned", fingerprint); + this.fptype = fptype; + this.fingerprint = checkNotNull(fingerprint, "fingerprint"); + this.delegate = ImmutableMap. builder() + .put("algorithm", algorithm) + .put("fptype", fptype) + .put("fingerprint", fingerprint).build(); + } + + /** + * This algorithm number octet describes the algorithm of the public key. + * + * @return most often {@code 1} for {@code RSA} or {@code 2} for {@code DSA}. + */ + public int getAlgorithm() { + return algorithm; + } + + /** + * The fingerprint fptype octet describes the message-digest algorithm used + * to calculate the fingerprint of the public key. + * + * @return most often {@code 1} for {@code SHA-1} + */ + public int getType() { + return fptype; + } + + /** + * The fingerprint calculated over the public key blob. + */ + public String getFingerprint() { + return fingerprint; + } + + public final static class Builder { + private int algorithm; + private int fptype; + private String fingerprint; + + /** + * @see SSHFPData#getAlgorithm() + */ + public SSHFPData.Builder algorithm(int algorithm) { + this.algorithm = algorithm; + return this; + } + + /** + * @see SSHFPData#getType() + */ + public SSHFPData.Builder fptype(int fptype) { + this.fptype = fptype; + return this; + } + + /** + * @see SSHFPData#getFingerprint() + */ + public SSHFPData.Builder fingerprint(String fingerprint) { + this.fingerprint = fingerprint; + return this; + } + + public SSHFPData build() { + return new SSHFPData(algorithm, fptype, fingerprint); + } + } + + public static SSHFPData.Builder builder() { + return new Builder(); + } + + // transient to avoid serializing by default, for example in json + private final transient ImmutableMap delegate; + + @Override + protected Map delegate() { + return delegate; + } +} diff --git a/providers/dynect/src/main/java/org/jclouds/dynect/v3/features/RecordApi.java b/providers/dynect/src/main/java/org/jclouds/dynect/v3/features/RecordApi.java index b0a51bc58b..dca82a1b0e 100644 --- a/providers/dynect/src/main/java/org/jclouds/dynect/v3/features/RecordApi.java +++ b/providers/dynect/src/main/java/org/jclouds/dynect/v3/features/RecordApi.java @@ -32,7 +32,9 @@ import org.jclouds.dynect.v3.domain.rdata.CNAMEData; import org.jclouds.dynect.v3.domain.rdata.MXData; import org.jclouds.dynect.v3.domain.rdata.NSData; import org.jclouds.dynect.v3.domain.rdata.PTRData; +import org.jclouds.dynect.v3.domain.rdata.SPFData; import org.jclouds.dynect.v3.domain.rdata.SRVData; +import org.jclouds.dynect.v3.domain.rdata.SSHFPData; import org.jclouds.dynect.v3.domain.rdata.TXTData; import org.jclouds.javax.annotation.Nullable; @@ -184,6 +186,19 @@ public interface RecordApi { */ SOARecord getSOA(String fqdn, long recordId) throws JobStillRunningException; + /** + * Gets the {@link SPFRecord} or null if not present. + * + * @param fqdn + * {@link RecordId#getFQDN()} + * @param recordId + * {@link RecordId#getId()} + * @return null if not found + * @throws JobStillRunningException + * if a different job in the session is still running + */ + Record getSPF(String fqdn, long recordId) throws JobStillRunningException; + /** * Gets the {@link SRVRecord} or null if not present. * @@ -197,6 +212,19 @@ public interface RecordApi { */ Record getSRV(String fqdn, long recordId) throws JobStillRunningException; + /** + * Gets the {@link SSHFPRecord} or null if not present. + * + * @param fqdn + * {@link RecordId#getFQDN()} + * @param recordId + * {@link RecordId#getId()} + * @return null if not found + * @throws JobStillRunningException + * if a different job in the session is still running + */ + Record getSSHFP(String fqdn, long recordId) throws JobStillRunningException; + /** * Gets the {@link TXTRecord} or null if not present. * diff --git a/providers/dynect/src/main/java/org/jclouds/dynect/v3/features/RecordAsyncApi.java b/providers/dynect/src/main/java/org/jclouds/dynect/v3/features/RecordAsyncApi.java index aa26198700..a4142f704f 100644 --- a/providers/dynect/src/main/java/org/jclouds/dynect/v3/features/RecordAsyncApi.java +++ b/providers/dynect/src/main/java/org/jclouds/dynect/v3/features/RecordAsyncApi.java @@ -49,7 +49,9 @@ import org.jclouds.dynect.v3.domain.rdata.CNAMEData; import org.jclouds.dynect.v3.domain.rdata.MXData; import org.jclouds.dynect.v3.domain.rdata.NSData; import org.jclouds.dynect.v3.domain.rdata.PTRData; +import org.jclouds.dynect.v3.domain.rdata.SPFData; import org.jclouds.dynect.v3.domain.rdata.SRVData; +import org.jclouds.dynect.v3.domain.rdata.SSHFPData; import org.jclouds.dynect.v3.domain.rdata.TXTData; import org.jclouds.dynect.v3.filters.AlwaysAddContentType; import org.jclouds.dynect.v3.filters.SessionManager; @@ -236,6 +238,16 @@ public interface RecordAsyncApi { @Fallback(NullOnNotFoundOr404.class) ListenableFuture getSOA(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId) throws JobStillRunningException; + /** + * @see RecordApi#getSPF + */ + @Named("GetSPFRecord") + @GET + @Path("/SPFRecord/{zone}/{fqdn}/{id}") + @SelectJson("data") + @Fallback(NullOnNotFoundOr404.class) + ListenableFuture> getSPF(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId) throws JobStillRunningException; + /** * @see RecordApi#getSRV */ @@ -246,6 +258,16 @@ public interface RecordAsyncApi { @Fallback(NullOnNotFoundOr404.class) ListenableFuture> getSRV(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId) throws JobStillRunningException; + /** + * @see RecordApi#getSSHFP + */ + @Named("GetSSHFPRecord") + @GET + @Path("/SSHFPRecord/{zone}/{fqdn}/{id}") + @SelectJson("data") + @Fallback(NullOnNotFoundOr404.class) + ListenableFuture> getSSHFP(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId) throws JobStillRunningException; + /** * @see RecordApi#getTXT */ diff --git a/providers/dynect/src/test/java/org/jclouds/dynect/v3/features/RecordApiExpectTest.java b/providers/dynect/src/test/java/org/jclouds/dynect/v3/features/RecordApiExpectTest.java index 41081a2817..40542a8dff 100644 --- a/providers/dynect/src/test/java/org/jclouds/dynect/v3/features/RecordApiExpectTest.java +++ b/providers/dynect/src/test/java/org/jclouds/dynect/v3/features/RecordApiExpectTest.java @@ -41,7 +41,9 @@ import org.jclouds.dynect.v3.parse.GetNSRecordResponseTest; import org.jclouds.dynect.v3.parse.GetPTRRecordResponseTest; import org.jclouds.dynect.v3.parse.GetRecordResponseTest; import org.jclouds.dynect.v3.parse.GetSOARecordResponseTest; +import org.jclouds.dynect.v3.parse.GetSPFRecordResponseTest; import org.jclouds.dynect.v3.parse.GetSRVRecordResponseTest; +import org.jclouds.dynect.v3.parse.GetSSHFPRecordResponseTest; import org.jclouds.dynect.v3.parse.GetTXTRecordResponseTest; import org.jclouds.dynect.v3.parse.ListRecordsResponseTest; import org.jclouds.http.HttpRequest; @@ -246,6 +248,27 @@ public class RecordApiExpectTest extends BaseDynECTApiExpectTest { assertNull(fail.getRecordApiForZone("jclouds.org").getSOA(soaId.getFQDN(), soaId.getId())); } + HttpRequest getSPF = HttpRequest.builder().method("GET") + .endpoint("https://api2.dynect.net/REST/SPFRecord/jclouds.org/jclouds.org/50976579") + .addHeader("API-Version", "3.3.8") + .addHeader(CONTENT_TYPE, APPLICATION_JSON) + .addHeader("Auth-Token", authToken).build(); + + HttpResponse spfResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/get_record_spf.json", APPLICATION_JSON)).build(); + + RecordId spfId = recordIdBuilder() + .zone("jclouds.org") + .fqdn("jclouds.org") + .type("SPF") + .id(50976579l).build(); + + public void testGetSPFWhenResponseIs2xx() { + DynECTApi success = requestsSendResponses(createSession, createSessionResponse, getSPF, spfResponse); + assertEquals(success.getRecordApiForZone("jclouds.org").getSPF(spfId.getFQDN(), spfId.getId()).toString(), + new GetSPFRecordResponseTest().expected().toString()); + } + HttpRequest getSRV = HttpRequest.builder().method("GET") .endpoint("https://api2.dynect.net/REST/SRVRecord/jclouds.org/jclouds.org/50976579") .addHeader("API-Version", "3.3.8") @@ -267,6 +290,27 @@ public class RecordApiExpectTest extends BaseDynECTApiExpectTest { new GetSRVRecordResponseTest().expected().toString()); } + HttpRequest getSSHFP = HttpRequest.builder().method("GET") + .endpoint("https://api2.dynect.net/REST/SSHFPRecord/jclouds.org/jclouds.org/50976579") + .addHeader("API-Version", "3.3.8") + .addHeader(CONTENT_TYPE, APPLICATION_JSON) + .addHeader("Auth-Token", authToken).build(); + + HttpResponse sshfpResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/get_record_sshfp.json", APPLICATION_JSON)).build(); + + RecordId sshfpId = recordIdBuilder() + .zone("jclouds.org") + .fqdn("jclouds.org") + .type("SSHFP") + .id(50976579l).build(); + + public void testGetSSHFPWhenResponseIs2xx() { + DynECTApi success = requestsSendResponses(createSession, createSessionResponse, getSSHFP, sshfpResponse); + assertEquals(success.getRecordApiForZone("jclouds.org").getSSHFP(sshfpId.getFQDN(), sshfpId.getId()).toString(), + new GetSSHFPRecordResponseTest().expected().toString()); + } + HttpRequest getTXT = HttpRequest.builder().method("GET") .endpoint("https://api2.dynect.net/REST/TXTRecord/jclouds.org/jclouds.org/50976579") .addHeader("API-Version", "3.3.8") diff --git a/providers/dynect/src/test/java/org/jclouds/dynect/v3/features/RecordApiLiveTest.java b/providers/dynect/src/test/java/org/jclouds/dynect/v3/features/RecordApiLiveTest.java index 9c58679472..380090d74e 100644 --- a/providers/dynect/src/test/java/org/jclouds/dynect/v3/features/RecordApiLiveTest.java +++ b/providers/dynect/src/test/java/org/jclouds/dynect/v3/features/RecordApiLiveTest.java @@ -42,7 +42,9 @@ import org.jclouds.dynect.v3.domain.rdata.MXData; import org.jclouds.dynect.v3.domain.rdata.NSData; import org.jclouds.dynect.v3.domain.rdata.PTRData; import org.jclouds.dynect.v3.domain.rdata.SOAData; +import org.jclouds.dynect.v3.domain.rdata.SPFData; import org.jclouds.dynect.v3.domain.rdata.SRVData; +import org.jclouds.dynect.v3.domain.rdata.SSHFPData; import org.jclouds.dynect.v3.domain.rdata.TXTData; import org.jclouds.dynect.v3.internal.BaseDynECTApiLiveTest; import org.testng.annotations.AfterClass; @@ -93,8 +95,12 @@ public class RecordApiLiveTest extends BaseDynECTApiLiveTest { record = checkPTRRecord(api.getPTR(recordId.getFQDN(), recordId.getId())); } else if ("SOA".equals(recordId.getType())) { record = checkSOARecord(api.getSOA(recordId.getFQDN(), recordId.getId())); + } else if ("SPF".equals(recordId.getType())) { + record = checkSPFRecord(api.getSPF(recordId.getFQDN(), recordId.getId())); } else if ("SRV".equals(recordId.getType())) { record = checkSRVRecord(api.getSRV(recordId.getFQDN(), recordId.getId())); + } else if ("SSHFP".equals(recordId.getType())) { + record = checkSSHFPRecord(api.getSSHFP(recordId.getFQDN(), recordId.getId())); } else if ("TXT".equals(recordId.getType())) { record = checkTXTRecord(api.getTXT(recordId.getFQDN(), recordId.getId())); } else { @@ -156,6 +162,12 @@ public class RecordApiLiveTest extends BaseDynECTApiLiveTest { return record; } + private Record checkSPFRecord(Record record) { + SPFData rdata = record.getRData(); + checkNotNull(rdata.getTxtdata(), "rdata.txtdata cannot be null for SPFRecord: %s", record); + return record; + } + private Record checkSRVRecord(Record record) { SRVData rdata = record.getRData(); checkNotNull(rdata.getPriority(), "rdata.priority cannot be null for SRVRecord: %s", record); @@ -165,6 +177,14 @@ public class RecordApiLiveTest extends BaseDynECTApiLiveTest { return record; } + private Record checkSSHFPRecord(Record record) { + SSHFPData rdata = record.getRData(); + checkNotNull(rdata.getAlgorithm(), "rdata.algorithm cannot be null for SSHFPRecord: %s", record); + checkNotNull(rdata.getType(), "rdata.type cannot be null for SSHFPRecord: %s", record); + checkNotNull(rdata.getFingerprint(), "rdata.fingerprint cannot be null for SSHFPRecord: %s", record); + return record; + } + private Record checkTXTRecord(Record record) { TXTData rdata = record.getRData(); checkNotNull(rdata.getTxtdata(), "rdata.txtdata cannot be null for TXTRecord: %s", record); diff --git a/providers/dynect/src/test/java/org/jclouds/dynect/v3/parse/GetSPFRecordResponseTest.java b/providers/dynect/src/test/java/org/jclouds/dynect/v3/parse/GetSPFRecordResponseTest.java new file mode 100644 index 0000000000..6d4ccc1e7c --- /dev/null +++ b/providers/dynect/src/test/java/org/jclouds/dynect/v3/parse/GetSPFRecordResponseTest.java @@ -0,0 +1,53 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.jclouds.dynect.v3.parse; + +import javax.ws.rs.Consumes; +import javax.ws.rs.core.MediaType; + +import org.jclouds.dynect.v3.domain.Record; +import org.jclouds.dynect.v3.domain.rdata.SPFData; +import org.jclouds.dynect.v3.internal.BaseDynECTParseTest; +import org.jclouds.rest.annotations.SelectJson; +import org.testng.annotations.Test; + +/** + * @author Adrian Cole + */ +@Test(groups = "unit") +public class GetSPFRecordResponseTest extends BaseDynECTParseTest> { + + @Override + public String resource() { + return "/get_record_spf.json"; + } + + @Override + @SelectJson("data") + @Consumes(MediaType.APPLICATION_JSON) + public Record expected() { + return Record. builder() + .zone("adrianc.zone.dynecttest.jclouds.org") + .fqdn("_http._tcp.www.jclouds.org.") + .type("SPF") + .id(50976579l) + .ttl(3600) + .rdata(SPFData.create("v=spf1 a -all")).build(); + } +} \ No newline at end of file diff --git a/providers/dynect/src/test/java/org/jclouds/dynect/v3/parse/GetSSHFPRecordResponseTest.java b/providers/dynect/src/test/java/org/jclouds/dynect/v3/parse/GetSSHFPRecordResponseTest.java new file mode 100644 index 0000000000..1f3d2db426 --- /dev/null +++ b/providers/dynect/src/test/java/org/jclouds/dynect/v3/parse/GetSSHFPRecordResponseTest.java @@ -0,0 +1,49 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.jclouds.dynect.v3.parse; + +import javax.ws.rs.Consumes; +import javax.ws.rs.core.MediaType; + +import org.jclouds.dynect.v3.domain.Record; +import org.jclouds.dynect.v3.domain.rdata.SSHFPData; +import org.jclouds.dynect.v3.internal.BaseDynECTParseTest; +import org.jclouds.rest.annotations.SelectJson; +import org.testng.annotations.Test; + +/** + * @author Adrian Cole + */ +@Test(groups = "unit") +public class GetSSHFPRecordResponseTest extends BaseDynECTParseTest> { + + @Override + public String resource() { + return "/get_record_sshfp.json"; + } + + @Override + @SelectJson("data") + @Consumes(MediaType.APPLICATION_JSON) + public Record expected() { + return Record. builder().zone("adrianc.zone.dynecttest.jclouds.org") + .fqdn("_http._tcp.www.jclouds.org.").type("SSHFP").id(50976579l).ttl(3600) + .rdata(SSHFPData.builder().algorithm(2).fptype(1).fingerprint("190E37C5B5DB9A1C455E648A41AF3CC83F99F102").build()).build(); + } +} \ No newline at end of file diff --git a/providers/dynect/src/test/resources/get_record_spf.json b/providers/dynect/src/test/resources/get_record_spf.json new file mode 100644 index 0000000000..d169b65a61 --- /dev/null +++ b/providers/dynect/src/test/resources/get_record_spf.json @@ -0,0 +1 @@ +{"status": "success", "data": {"zone": "adrianc.zone.dynecttest.jclouds.org", "ttl": 3600, "fqdn": "_http._tcp.www.jclouds.org.", "record_type": "SPF", "rdata": {"txtdata": "v=spf1 a -all"}, "record_id": 50976579}, "job_id": 273523378, "msgs": [{"INFO": "get: Found the record", "SOURCE": "API-B", "ERR_CD": null, "LVL": "INFO"}]} \ No newline at end of file diff --git a/providers/dynect/src/test/resources/get_record_sshfp.json b/providers/dynect/src/test/resources/get_record_sshfp.json new file mode 100644 index 0000000000..7c3f75d014 --- /dev/null +++ b/providers/dynect/src/test/resources/get_record_sshfp.json @@ -0,0 +1 @@ +{"status": "success", "data": {"zone": "adrianc.zone.dynecttest.jclouds.org", "ttl": 3600, "fqdn": "_http._tcp.www.jclouds.org.", "record_type": "SSHFP", "rdata": {"fptype": 1, "algorithm": 2, "fingerprint": "190E37C5B5DB9A1C455E648A41AF3CC83F99F102"}, "record_id": 50976579}, "job_id": 273523378, "msgs": [{"INFO": "get: Found the record", "SOURCE": "API-B", "ERR_CD": null, "LVL": "INFO"}]} \ No newline at end of file