Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for passing custom user agent header. #28

Merged
merged 5 commits into from
Apr 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions Okta.Auth.Sdk.UnitTests/AuthenticationClientShould.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,36 @@ public async Task ForgotPasswordWithCallFactor()
.Be("https://dotnet.oktapreview.com/api/v1/authn/recovery/factors/CALL/resend");
}

[Fact]
public async Task AddHeadersToForgotPasswordRequest()
{
var forgotPasswordOptions = new ForgotPasswordOptions()
{
FactorType = FactorType.Call,
RelayState = "/myapp/some/deep/link/i/want/to/return/to",
UserName = "bob-user@test.com",
UserAgent = "baz",
XForwardedFor = "bar",
};

var mockRequestExecutor = Substitute.For<IRequestExecutor>();
mockRequestExecutor
.PostAsync(Arg.Any<string>(), Arg.Any<IEnumerable<KeyValuePair<string, string>>>(), Arg.Any<string>(), Arg.Any<CancellationToken>())
.Returns(new HttpResponse<string>() { StatusCode = 200 });

var authnClient = new TesteableAuthnClient(mockRequestExecutor);

await authnClient.ForgotPasswordAsync(forgotPasswordOptions);

await mockRequestExecutor.Received().PostAsync(
"/api/v1/authn/recovery/password",
Arg.Is<IEnumerable<KeyValuePair<string, string>>>(headers =>
headers.Any(kvp => kvp.Key == "User-Agent" && kvp.Value == "baz") &&
headers.Any(kvp => kvp.Key == "X-Forwarded-For" && kvp.Value == "bar")),
Arg.Any<string>(),
CancellationToken.None);
}

[Fact]
public async Task ResetPassword()
{
Expand Down Expand Up @@ -221,6 +251,65 @@ await mockRequestExecutor.Received().PostAsync(
CancellationToken.None);
}

[Fact]
public async Task AddHeadersToAuthenticationRequest()
{
var authOptions = new AuthenticateOptions()
{
Username = "foo",
Password = "bar",
XForwardedFor = "baz",
UserAgent = "qux",
DeviceFingerprint = "quux",
};

var mockRequestExecutor = Substitute.For<IRequestExecutor>();
mockRequestExecutor
.PostAsync(Arg.Any<string>(), Arg.Any<IEnumerable<KeyValuePair<string, string>>>(), Arg.Any<string>(), Arg.Any<CancellationToken>())
.Returns(new HttpResponse<string>() { StatusCode = 200 });

var authnClient = new TesteableAuthnClient(mockRequestExecutor);

await authnClient.AuthenticateAsync(authOptions);

await mockRequestExecutor.Received().PostAsync(
"/api/v1/authn",
Arg.Is<IEnumerable<KeyValuePair<string, string>>>(headers =>
headers.Any(kvp => kvp.Key == "X-Forwarded-For" && kvp.Value == "baz") &&
headers.Any(kvp => kvp.Key == "User-Agent" && kvp.Value == "qux") &&
headers.Any(kvp => kvp.Key == "X-Device-Fingerprint" && kvp.Value == "quux")),
"{\"username\":\"foo\",\"password\":\"bar\"}",
CancellationToken.None);
}

[Fact]
public async Task AddHeadersToAuthenticationWithActivationTokenRequest()
{
var authOptions = new AuthenticateWithActivationTokenOptions()
{
ActivationToken = "foo",
XForwardedFor = "baz",
UserAgent = "bar",
};

var mockRequestExecutor = Substitute.For<IRequestExecutor>();
mockRequestExecutor
.PostAsync(Arg.Any<string>(), Arg.Any<IEnumerable<KeyValuePair<string, string>>>(), Arg.Any<string>(), Arg.Any<CancellationToken>())
.Returns(new HttpResponse<string>() { StatusCode = 200 });

var authnClient = new TesteableAuthnClient(mockRequestExecutor);

await authnClient.AuthenticateAsync(authOptions);

await mockRequestExecutor.Received().PostAsync(
"/api/v1/authn",
Arg.Is<IEnumerable<KeyValuePair<string, string>>>(headers =>
headers.Any(kvp => kvp.Key == "X-Forwarded-For" && kvp.Value == "baz") &&
headers.Any(kvp => kvp.Key == "User-Agent" && kvp.Value == "bar") ),
"{\"token\":\"foo\"}",
CancellationToken.None);
}

[Fact]
public async Task SendTokenWhenAuthenticatingWithActivationToken()
{
Expand Down Expand Up @@ -869,6 +958,36 @@ await mockRequestExecutor.Received().PostAsync(
CancellationToken.None);
}

[Fact]
public async Task AddHeadersToUnlockAccountRequest()
{
var unlockAccountOptions = new UnlockAccountOptions()
{
FactorType = new FactorType("sms"),
RelayState = "/myapp/some/deep/link/i/want/to/return/to",
Username = "dade.murphy@example.com",
UserAgent = "baz",
XForwardedFor = "bar",
};

var mockRequestExecutor = Substitute.For<IRequestExecutor>();
mockRequestExecutor
.PostAsync(Arg.Any<string>(), Arg.Any<IEnumerable<KeyValuePair<string, string>>>(), Arg.Any<string>(), Arg.Any<CancellationToken>())
.Returns(new HttpResponse<string>() { StatusCode = 200 });

var authnClient = new TesteableAuthnClient(mockRequestExecutor);

await authnClient.UnlockAccountAsync(unlockAccountOptions);

await mockRequestExecutor.Received().PostAsync(
"/api/v1/authn/recovery/unlock",
Arg.Is<IEnumerable<KeyValuePair<string, string>>>(headers =>
headers.Any(kvp => kvp.Key == "User-Agent" && kvp.Value == "baz") &&
headers.Any(kvp => kvp.Key == "X-Forwarded-For" && kvp.Value == "bar")),
Arg.Any<string>(),
CancellationToken.None);
}

[Theory]
[InlineData("sms")]
[InlineData("call")]
Expand Down
12 changes: 12 additions & 0 deletions Okta.Auth.Sdk/AuthenticateOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ public class AuthenticateOptions
/// <value>The device fingerprint</value>
public string DeviceFingerprint { get; set; }

/// <summary>
/// Gets or sets the value for `x-forwarded-for` header.
/// </summary>
/// <value>The value for `x-forwarded-for` header.</value>
public string XForwardedFor { get; set; }

/// <summary>
/// Gets or sets the user agent.
/// </summary>
/// <value>The user agent.</value>
public string UserAgent { get; set; }

/// <summary>
/// Gets or sets the state token
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions Okta.Auth.Sdk/AuthenticateWithActivationTokenOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,17 @@ public class AuthenticateWithActivationTokenOptions
/// </summary>
/// <value>The activation token</value>
public string ActivationToken { get; set; }

/// <summary>
/// Gets or sets the value for `x-forwarded-for` header.
/// </summary>
/// <value>The value for `x-forwarded-for` header.</value>
public string XForwardedFor { get; set; }

/// <summary>
/// Gets or sets the user agent.
/// </summary>
/// <value>The user agent.</value>
public string UserAgent { get; set; }
}
}
80 changes: 62 additions & 18 deletions Okta.Auth.Sdk/AuthenticationClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ protected AuthenticationClient(IDataStore dataStore, OktaClientConfiguration con
request.Headers["X-Device-Fingerprint"] = authenticateOptions.DeviceFingerprint;
}

if (!string.IsNullOrEmpty(authenticateOptions.XForwardedFor))
{
request.Headers["X-Forwarded-For"] = authenticateOptions.XForwardedFor;
}

if (!string.IsNullOrEmpty(authenticateOptions.UserAgent))
{
request.Headers["User-Agent"] = authenticateOptions.UserAgent;
}

return await PostAsync<AuthenticationResponse>(
request, cancellationToken).ConfigureAwait(false);
}
Expand All @@ -99,12 +109,24 @@ protected AuthenticationClient(IDataStore dataStore, OktaClientConfiguration con
ActivationToken = authenticateOptions.ActivationToken,
};

return await PostAsync<AuthenticationResponse>(
new HttpRequest
{
Uri = "/api/v1/authn",
Payload = authenticationRequest,
}, cancellationToken).ConfigureAwait(false);
var request = new HttpRequest
{
Uri = "/api/v1/authn",
Payload = authenticationRequest,

};

if (!string.IsNullOrEmpty(authenticateOptions.UserAgent))
{
request.Headers["User-Agent"] = authenticateOptions.UserAgent;
}

if (!string.IsNullOrEmpty(authenticateOptions.XForwardedFor))
{
request.Headers["X-Forwarded-For"] = authenticateOptions.XForwardedFor;
}

return await PostAsync<AuthenticationResponse>(request, cancellationToken).ConfigureAwait(false);
}

/// <inheritdoc/>
Expand Down Expand Up @@ -135,12 +157,23 @@ protected AuthenticationClient(IDataStore dataStore, OktaClientConfiguration con
FactorType = forgotPasswordOptions.FactorType,
};

return await PostAsync<AuthenticationResponse>(
new HttpRequest
{
Uri = "/api/v1/authn/recovery/password",
Payload = forgotPasswordRequest,
}, cancellationToken).ConfigureAwait(false);
var request = new HttpRequest
{
Uri = "/api/v1/authn/recovery/password",
Payload = forgotPasswordRequest,
};

if (!string.IsNullOrEmpty(forgotPasswordOptions.UserAgent))
{
request.Headers["User-Agent"] = forgotPasswordOptions.UserAgent;
}

if (!string.IsNullOrEmpty(forgotPasswordOptions.XForwardedFor))
{
request.Headers["X-Forwarded-For"] = forgotPasswordOptions.XForwardedFor;
}

return await PostAsync<AuthenticationResponse>(request, cancellationToken).ConfigureAwait(false);
}

/// <inheritdoc/>
Expand Down Expand Up @@ -506,12 +539,23 @@ protected AuthenticationClient(IDataStore dataStore, OktaClientConfiguration con
Username = unlockAccountOptions.Username,
};

return await PostAsync<AuthenticationResponse>(
new HttpRequest
{
Uri = $"/api/v1/authn/recovery/unlock",
Payload = unlockAccountRequest,
}, cancellationToken).ConfigureAwait(false);
var request = new HttpRequest
{
Uri = $"/api/v1/authn/recovery/unlock",
Payload = unlockAccountRequest,
};

if (!string.IsNullOrEmpty(unlockAccountOptions.UserAgent))
{
request.Headers["User-Agent"] = unlockAccountOptions.UserAgent;
}

if (!string.IsNullOrEmpty(unlockAccountOptions.XForwardedFor))
{
request.Headers["X-Forwarded-For"] = unlockAccountOptions.XForwardedFor;
}

return await PostAsync<AuthenticationResponse>(request, cancellationToken).ConfigureAwait(false);
}

/// <inheritdoc/>
Expand Down
12 changes: 12 additions & 0 deletions Okta.Auth.Sdk/ForgotPasswordOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,17 @@ public class ForgotPasswordOptions
/// </summary>
/// <value>The factor type</value>
public FactorType FactorType { get; set; }

/// <summary>
/// Gets or sets the value for `x-forwarded-for` header.
/// </summary>
/// <value>The value for `x-forwarded-for` header.</value>
public string XForwardedFor { get; set; }

/// <summary>
/// Gets or sets the user agent.
/// </summary>
/// <value>The user agent.</value>
public string UserAgent { get; set; }
}
}
12 changes: 12 additions & 0 deletions Okta.Auth.Sdk/UnlockAccountOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,17 @@ public class UnlockAccountOptions
/// </summary>
/// <value>The factor type</value>
public FactorType FactorType { get; set; }

/// <summary>
/// Gets or sets the value for `x-forwarded-for` header.
/// </summary>
/// <value>The value for `x-forwarded-for` header.</value>
public string XForwardedFor { get; set; }

/// <summary>
/// Gets or sets the user agent.
/// </summary>
/// <value>The user agent.</value>
public string UserAgent { get; set; }
}
}
23 changes: 23 additions & 0 deletions Okta.Sdk.Abstractions.UnitTests/DefaultDataStoreShould.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,29 @@ await mockRequestExecutor.Received().GetAsync(
CancellationToken.None);
}


[Fact]
public async Task DoNotOvewriteUserAgentWhenProvided()
{
var mockRequestExecutor = Substitute.For<IRequestExecutor>();
mockRequestExecutor
.GetAsync(Arg.Any<string>(), Arg.Any<IEnumerable<KeyValuePair<string, string>>>(), Arg.Any<CancellationToken>())
.Returns(new HttpResponse<string>() { StatusCode = 200 });

var dataStore = new DefaultDataStore(mockRequestExecutor, new DefaultSerializer(), new ResourceFactory(null, null, null), NullLogger.Instance, new UserAgentBuilder("okta-sdk-dotnet", UserAgentHelper.SdkVersion));
var request = new HttpRequest { Uri = "https://foo.dev" };
request.Headers["User-Agent"] = "foo bar baz";

await dataStore.GetAsync<TestResource>(request, new RequestContext(), CancellationToken.None);

// Assert that the request sent to the RequestExecutor included the User-Agent header
await mockRequestExecutor.Received().GetAsync(
"https://foo.dev",
Arg.Is<IEnumerable<KeyValuePair<string, string>>>(
headers => headers.Any(kvp => kvp.Key == "User-Agent" && kvp.Value == "foo bar baz")),
CancellationToken.None);
}

[Fact]
public async Task AddContextUserAgentToRequests()
{
Expand Down
6 changes: 5 additions & 1 deletion Okta.Sdk.Abstractions/DefaultDataStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,11 @@ private void AddUserAgent(HttpRequest request)
request.Headers = new Dictionary<string, string>();
}

request.Headers["User-Agent"] = _userAgentBuilder.GetUserAgent();
// User-Agent is not overwritten if provided
if (!request.Headers.ContainsKey("User-Agent") || string.IsNullOrEmpty(request.Headers["User-Agent"]))
{
request.Headers["User-Agent"] = _userAgentBuilder.GetUserAgent();
}
}

private static void ApplyContextToRequest(HttpRequest request, RequestContext context)
Expand Down
Loading