Skip to content

Commit

Permalink
added command line argument to configure registration contact
Browse files Browse the repository at this point in the history
added command line argument to configure agreement uri
removed hard coded contact email
improved unit tests
  • Loading branch information
oocx committed Nov 23, 2015
1 parent fd1a356 commit 9421142
Show file tree
Hide file tree
Showing 12 changed files with 309 additions and 356 deletions.
Binary file modified .vs/ACME.net/v14/.suo
Binary file not shown.
9 changes: 7 additions & 2 deletions src/Oocx.ACME.Console/AcmeProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ private async Task<bool> AuthorizeForDomain(string domain)

private async Task RegisterWithServer()
{
var registration = await client.RegisterAsync(options.AcceptTermsOfService);
var registration = await client.RegisterAsync(options.AcceptTermsOfService ? options.TermsOfServiceUri : null, options.Contact);
Info($"Terms of service: {registration.Agreement}");
Verbose($"Created at: {registration.CreatedAt}");
Verbose($"Id: {registration.Id}");
Expand All @@ -174,7 +174,12 @@ private async Task RegisterWithServer()
if (!string.IsNullOrWhiteSpace(registration.Location) && options.AcceptTermsOfService)
{
Info("accepting terms of service");
await client.UpdateRegistrationAsync(registration.Location, registration.Agreement);
if (!string.Equals(registration.Agreement, options.TermsOfServiceUri))
{
Error($"Cannot accept terms of service. The terms of service uri is '{registration.Agreement}', expected it to be '{options.TermsOfServiceUri}'.");
return;
}
await client.UpdateRegistrationAsync(registration.Location, registration.Agreement, options.Contact);
}
}

Expand Down
8 changes: 7 additions & 1 deletion src/Oocx.ACME.Console/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ public class Options
[Value(0, HelpText = "The names of the domains for which you want to request a certificate.", Required = true, MetaName = "domains")]
public IEnumerable<string> Domains { get; set; }

[Option('a', "acceptTOS", HelpText = "Accept the terms of service of the ACME server")]
[Option('a', "acceptTermsOfService", HelpText = "Accept the terms of service of the ACME server")]
public bool AcceptTermsOfService { get; set; }

[Option('t', "termsOfServiceUri", HelpText = "The uri of the terms of service that you accept.", Default = "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf")]
public string TermsOfServiceUri { get; set; }

[Option('i', "ignoreSSLErrors", HelpText = "Ignore SSL certificate errors for the HTTPS connection to the ACME server (useful for debugging messages with fiddler)")]
public bool IgnoreSSLCertificateErrors { get; set; }

Expand All @@ -43,5 +46,8 @@ public class Options

[Option('b', "iisBinding", HelpText = "The IIS binding that should be configured to use the new certificate. Syntax: ip:port:hostname, for example '*:443:www.example.com' (used with --serverConfigurationProvider iis). If you do not specifiy a binding, the provider will try to find a matching binding. It will create a binding if no matching binding exists.")]
public string IISBinidng { get; set; }

[Option('m', "contact", HelpText = "The contact information for the registration request. Example: mailto:[email protected]")]
public string[] Contact { get; set; }
}
}
71 changes: 54 additions & 17 deletions src/Oocx.ACME.Tests/ClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Security.Cryptography;
using System.Threading.Tasks;
using FluentAssertions;
using Oocx.ACME.Client;
using Oocx.ACME.Protocol;
using WorldDomination.Net.Http;
using Oocx.ACME.Tests.FakeHttp;
using Xunit;

namespace Oocx.ACME.Tests
{
public class ClientTests
{


[Fact]
public async Task Should_get_a_Directory_object_from_the_default_endpoint()
{
// Arrange
var http = new FakeHttpMessageHandler("http://baseaddress/");
var directory = new Directory();
var response = new HttpResponseMessage(HttpStatusCode.OK) {Content = new ObjectContent<Directory>(directory, new JsonMediaTypeFormatter()) };
response.Headers.Add("Replay-Nonce", "nonce");
var handler = new FakeHttpMessageHandler("http://baseaddress/directory", response);
var client = new HttpClient(handler) { BaseAddress = new Uri("http://baseAddress")};

var client =
http.RequestTo("directory").Returns(directory).WithNonce("nonce")
.GetHttpClient();

var sut = new AcmeClient(client, new RSACryptoServiceProvider());

// Act
Expand All @@ -37,28 +40,62 @@ public async Task Should_get_a_Directory_object_from_the_default_endpoint()
public async Task Should_post_a_valid_Registration_message()
{
// Arrange
var http = new FakeHttpMessageHandler("http://baseaddress/");
var directory = new Directory() { NewRegistration = new Uri("http://baseaddress/registration")};
var response1 = new HttpResponseMessage(HttpStatusCode.OK) { Content = new ObjectContent<Directory>(directory, new JsonMediaTypeFormatter()) };
response1.Headers.Add("Replay-Nonce", "nonce");

var registration = new RegistrationResponse();
var response2 = new HttpResponseMessage(HttpStatusCode.OK) { Content = new ObjectContent<RegistrationResponse>(registration, new JsonMediaTypeFormatter()) };
response2.Headers.Add("Replay-Nonce", "nonce");

var handler = new FakeHttpMessageHandler(new Dictionary<string, HttpResponseMessage>()
var client =
http.RequestTo("directory").Returns(directory).WithNonce("nonce").
RequestTo("registration").Returns(registration).WithNonce("nonce").
GetHttpClient();

var sut = new AcmeClient(client, new RSACryptoServiceProvider());

// Act
var registrationResponse = await sut.RegisterAsync("agreementUri", new []{ "mailto:[email protected]"});

// Assert
registrationResponse.Should().NotBeNull();
http.ReceivedRequestsTo("directory").Single().HasMethod(HttpMethod.Get);
http.ReceivedRequestsTo("registration").Single().HasMethod(HttpMethod.Post).HasJwsPayload<NewRegistrationRequest>(r =>
{
{"http://baseaddress/directory", response1 },
{"http://baseaddress/registration", response2 }
r.Agreement.Should().Be("agreementUri");
r.Contact.Should().Contain("mailto:[email protected]");
});
}

[Fact]
public async Task Should_POST_to_get_registration_details_if_the_registration_already_exists()
{
// Arrange
var http = new FakeHttpMessageHandler("http://baseaddress/");
var directory = new Directory() { NewRegistration = new Uri("http://baseaddress/registration") };
var registration = new RegistrationResponse();

var client =
http.RequestTo("directory").Returns(directory).WithNonce("nonce").
RequestTo("registration").Returns(new Problem(), "application/problem+json").HasStatusCode(HttpStatusCode.Conflict).WithHeader("Location", "http://baseaddress/existingreguri").WithNonce("nonce").
RequestTo("existingreguri").Returns(registration).WithNonce("nonce").
GetHttpClient();

var client = new HttpClient(handler) { BaseAddress = new Uri("http://baseAddress") };
var sut = new AcmeClient(client, new RSACryptoServiceProvider());

// Act
var registrationResponse = await sut.RegisterAsync(false);
var registrationResponse = await sut.RegisterAsync("agreementUri", new[] { "mailto:[email protected]" });

// Assert
registrationResponse.Should().NotBeNull();
http.ReceivedRequestsTo("directory").Single().HasMethod(HttpMethod.Get);
http.ReceivedRequestsTo("registration").Single().HasMethod(HttpMethod.Post).HasJwsPayload<NewRegistrationRequest>(r =>
{
r.Agreement.Should().Be("agreementUri");
r.Contact.Should().Contain("mailto:[email protected]");
});
http.ReceivedRequestsTo("existingreguri").Single().HasMethod(HttpMethod.Post).HasJwsPayload<UpdateRegistrationRequest>(r =>
{
r.Agreement.Should().BeNull();
r.Contact.Should().BeNull();
});
}
}
}
59 changes: 59 additions & 0 deletions src/Oocx.ACME.Tests/FakeHttp/FakeHttpMessageHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions.Execution;

namespace Oocx.ACME.Tests.FakeHttp
{
public class FakeHttpMessageHandler : HttpMessageHandler
{
private readonly Queue<FakeRequestConfiguration> requestConfigurations = new Queue<FakeRequestConfiguration>();

private readonly List<HttpRequestMessage> actualRequests = new List<HttpRequestMessage>();

public string BaseAddress { get; }

public FakeHttpMessageHandler(string baseAddress)
{
this.BaseAddress = baseAddress;
}

public FakeRequestConfiguration RequestTo(string uri)
{
return new FakeRequestConfiguration(this) { Uri = BaseAddress + uri };
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Content = await PreventDisposeContentWrapper.CreateWrapperAsync(request.Content);

actualRequests.Add(request);

var expected = requestConfigurations.Dequeue();
if (!request.RequestUri.Equals(new Uri(expected.Uri, UriKind.RelativeOrAbsolute)))
{
throw new AssertionFailedException($"expected request to '{expected.Uri}', but got request to '{request.RequestUri}'");
}

return expected.GetResponseMessage();
}

public HttpRequestMessage[] ReceivedRequestsTo(string uri)
{
var requests = actualRequests.Where(r => r.RequestUri.Equals(new Uri(BaseAddress + uri))).ToArray();
if (!requests.Any())
{
throw new AssertionFailedException($"expected a request to {uri}, but only got the following requests: {string.Join(", ", actualRequests.Select(r => r.RequestUri.ToString()))}");
}
return requests;
}

public void Enqueue(FakeRequestConfiguration fakeRequestConfiguration)
{
requestConfigurations.Enqueue(fakeRequestConfiguration);
}
}
}
74 changes: 74 additions & 0 deletions src/Oocx.ACME.Tests/FakeHttp/FakeRequestConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;

namespace Oocx.ACME.Tests.FakeHttp
{
public class FakeRequestConfiguration
{
private readonly FakeHttpMessageHandler fakeHttpMessageHandler;
private static readonly JsonMediaTypeFormatter Formatter = new JsonMediaTypeFormatter();

public FakeRequestConfiguration(FakeHttpMessageHandler fakeHttpMessageHandler)
{
this.fakeHttpMessageHandler = fakeHttpMessageHandler;
fakeHttpMessageHandler.Enqueue(this);
}

public string Uri { get; set; }

public ObjectContent Content { get; set; }

public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();

public HttpStatusCode StatusCode { get; set; } = HttpStatusCode.OK;

public FakeRequestConfiguration Returns<T>(T content, string contentType = "application/json")
{
Content = new ObjectContent<T>(content, Formatter);
Content.Headers.ContentType.MediaType = contentType;
return this;
}

public FakeRequestConfiguration HasStatusCode(HttpStatusCode statusCode)
{
StatusCode = statusCode;
return this;
}

public FakeRequestConfiguration WithHeader(string key, string value)
{
Headers[key] = value;
return this;
}

public FakeRequestConfiguration WithNonce(string value)
{
Headers["Replay-Nonce"] = value;
return this;
}

public FakeRequestConfiguration RequestTo(string uri)
{
return fakeHttpMessageHandler.RequestTo(uri);
}

public HttpResponseMessage GetResponseMessage()
{
var response = new HttpResponseMessage(StatusCode) { Content = Content};
foreach (var header in Headers)
{
response.Headers.Add(header.Key, header.Value);
}
return response;
}

public HttpClient GetHttpClient()
{
var client = new HttpClient(fakeHttpMessageHandler) { BaseAddress = new Uri(fakeHttpMessageHandler.BaseAddress, UriKind.Absolute) };
return client;
}
}
}
48 changes: 48 additions & 0 deletions src/Oocx.ACME.Tests/FakeHttp/HttpRequestMessageAssertions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Net.Http;
using System.Text;
using FluentAssertions;
using Newtonsoft.Json;
using Oocx.ACME.Jose;
using Oocx.Asn1PKCS.Asn1BaseTypes;

namespace Oocx.ACME.Tests.FakeHttp
{
public static class HttpRequestMessageAssertions
{
public static HttpRequestMessage HasContent(this HttpRequestMessage request, Action<HttpContent> contentAssertion)
{
contentAssertion(request.Content);
return request;
}

public static HttpRequestMessage HasContent<T>(this HttpRequestMessage request, Action<T> contentAssertion)
{
contentAssertion(request.Content.ReadAsAsync<T>().Result);
return request;
}

public static HttpRequestMessage HasJwsPayload<T>(this HttpRequestMessage request, Action<T> contentAssertion)
{
var message = request.Content.ReadAsAsync<JWSMessage>().Result;
var contentJson = Encoding.UTF8.GetString(message.Payload.Base64UrlDecode());
var content = JsonConvert.DeserializeObject<T>(contentJson);

contentAssertion(content);
return request;
}

public static HttpRequestMessage HasHeader(this HttpRequestMessage request, string headerName, string headerValue)
{
request.Headers.Should().Contain(headerName);
request.Headers.GetValues(headerName).Should().Contain(headerValue);
return request;
}

public static HttpRequestMessage HasMethod(this HttpRequestMessage request, HttpMethod method)
{
request.Method.Should().Be(method);
return request;
}
}
}
Loading

0 comments on commit 9421142

Please sign in to comment.