001/* 002 * acme4j - Java ACME client 003 * 004 * Copyright (C) 2015 Richard "Shred" Körber 005 * http://acme4j.shredzone.org 006 * 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 013 */ 014package org.shredzone.acme4j.challenge; 015 016import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; 017import static org.assertj.core.api.Assertions.assertThat; 018import static org.assertj.core.api.Assertions.within; 019import static org.junit.jupiter.api.Assertions.assertThrows; 020import static org.shredzone.acme4j.toolbox.TestUtils.getJSON; 021import static org.shredzone.acme4j.toolbox.TestUtils.url; 022 023import java.net.HttpURLConnection; 024import java.net.URI; 025import java.net.URL; 026import java.time.Duration; 027import java.time.Instant; 028import java.time.temporal.ChronoUnit; 029import java.util.Optional; 030 031import org.assertj.core.api.AutoCloseableSoftAssertions; 032import org.junit.jupiter.api.Test; 033import org.shredzone.acme4j.Login; 034import org.shredzone.acme4j.Status; 035import org.shredzone.acme4j.exception.AcmeProtocolException; 036import org.shredzone.acme4j.provider.TestableConnectionProvider; 037import org.shredzone.acme4j.toolbox.JSON; 038import org.shredzone.acme4j.toolbox.JSONBuilder; 039import org.shredzone.acme4j.toolbox.TestUtils; 040 041/** 042 * Unit tests for {@link Challenge}. 043 */ 044public class ChallengeTest { 045 private final URL locationUrl = url("https://example.com/acme/some-location"); 046 047 /** 048 * Test that after unmarshaling, the challenge properties are set correctly. 049 */ 050 @Test 051 public void testUnmarshal() { 052 var challenge = new Challenge(TestUtils.login(), getJSON("genericChallenge")); 053 054 // Test unmarshalled values 055 try (var softly = new AutoCloseableSoftAssertions()) { 056 softly.assertThat(challenge.getType()).isEqualTo("generic-01"); 057 softly.assertThat(challenge.getStatus()).isEqualTo(Status.INVALID); 058 softly.assertThat(challenge.getLocation()).isEqualTo(url("http://example.com/challenge/123")); 059 softly.assertThat(challenge.getValidated().orElseThrow()) 060 .isCloseTo("2015-12-12T17:19:36.336Z", within(1, ChronoUnit.MILLIS)); 061 softly.assertThat(challenge.getJSON().get("type").asString()).isEqualTo("generic-01"); 062 softly.assertThat(challenge.getJSON().get("url").asURL()).isEqualTo(url("http://example.com/challenge/123")); 063 064 var error = challenge.getError().orElseThrow(); 065 softly.assertThat(error.getType()).isEqualTo(URI.create("urn:ietf:params:acme:error:incorrectResponse")); 066 softly.assertThat(error.getDetail().orElseThrow()).isEqualTo("bad token"); 067 softly.assertThat(error.getInstance().orElseThrow()) 068 .isEqualTo(URI.create("http://example.com/documents/faq.html")); 069 } 070 } 071 072 /** 073 * Test that {@link Challenge#prepareResponse(JSONBuilder)} contains the type. 074 */ 075 @Test 076 public void testRespond() { 077 var challenge = new Challenge(TestUtils.login(), getJSON("genericChallenge")); 078 079 var response = new JSONBuilder(); 080 challenge.prepareResponse(response); 081 082 assertThatJson(response.toString()).isEqualTo("{}"); 083 } 084 085 /** 086 * Test that an exception is thrown on challenge type mismatch. 087 */ 088 @Test 089 public void testNotAcceptable() { 090 assertThrows(AcmeProtocolException.class, () -> 091 new Http01Challenge(TestUtils.login(), getJSON("dnsChallenge")) 092 ); 093 } 094 095 /** 096 * Test that a challenge can be triggered. 097 */ 098 @Test 099 public void testTrigger() throws Exception { 100 var provider = new TestableConnectionProvider() { 101 @Override 102 public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { 103 assertThat(url).isEqualTo(locationUrl); 104 assertThatJson(claims.toString()).isEqualTo(getJSON("triggerHttpChallengeRequest").toString()); 105 assertThat(login).isNotNull(); 106 return HttpURLConnection.HTTP_OK; 107 } 108 109 @Override 110 public JSON readJsonResponse() { 111 return getJSON("triggerHttpChallengeResponse"); 112 } 113 }; 114 115 var login = provider.createLogin(); 116 117 var challenge = new Http01Challenge(login, getJSON("triggerHttpChallenge")); 118 119 challenge.trigger(); 120 121 assertThat(challenge.getStatus()).isEqualTo(Status.PENDING); 122 assertThat(challenge.getLocation()).isEqualTo(locationUrl); 123 124 provider.close(); 125 } 126 127 /** 128 * Test that a challenge is properly updated. 129 */ 130 @Test 131 public void testUpdate() throws Exception { 132 var provider = new TestableConnectionProvider() { 133 @Override 134 public int sendSignedPostAsGetRequest(URL url, Login login) { 135 assertThat(url).isEqualTo(locationUrl); 136 return HttpURLConnection.HTTP_OK; 137 } 138 139 @Override 140 public JSON readJsonResponse() { 141 return getJSON("updateHttpChallengeResponse"); 142 } 143 }; 144 145 var login = provider.createLogin(); 146 147 var challenge = new Http01Challenge(login, getJSON("triggerHttpChallengeResponse")); 148 149 challenge.update(); 150 151 assertThat(challenge.getStatus()).isEqualTo(Status.VALID); 152 assertThat(challenge.getLocation()).isEqualTo(locationUrl); 153 154 provider.close(); 155 } 156 157 /** 158 * Test that a challenge is properly updated, with Retry-After header. 159 */ 160 @Test 161 public void testUpdateRetryAfter() throws Exception { 162 var retryAfter = Instant.now().plus(Duration.ofSeconds(30)); 163 164 var provider = new TestableConnectionProvider() { 165 @Override 166 public int sendSignedPostAsGetRequest(URL url, Login login) { 167 assertThat(url).isEqualTo(locationUrl); 168 return HttpURLConnection.HTTP_OK; 169 } 170 171 @Override 172 public JSON readJsonResponse() { 173 return getJSON("updateHttpChallengeResponse"); 174 } 175 176 @Override 177 public Optional<Instant> getRetryAfter() { 178 return Optional.of(retryAfter); 179 } 180 }; 181 182 var login = provider.createLogin(); 183 184 var challenge = new Http01Challenge(login, getJSON("triggerHttpChallengeResponse")); 185 var returnedRetryAfter = challenge.fetch(); 186 assertThat(returnedRetryAfter).hasValue(retryAfter); 187 188 assertThat(challenge.getStatus()).isEqualTo(Status.VALID); 189 assertThat(challenge.getLocation()).isEqualTo(locationUrl); 190 191 provider.close(); 192 } 193 194 /** 195 * Test that unmarshalling something different like a challenge fails. 196 */ 197 @Test 198 public void testBadUnmarshall() { 199 assertThrows(AcmeProtocolException.class, () -> 200 new Challenge(TestUtils.login(), getJSON("updateAccountResponse")) 201 ); 202 } 203 204}