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 proxy server support #24

Merged
merged 4 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
72 changes: 71 additions & 1 deletion duo-universal-sdk/src/main/java/com/duosecurity/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ public class Client {

private Boolean useDuoCodeAttribute;

private String proxyHost;

private Integer proxyPort;

protected DuoConnector duoConnector;

private String userAgent;
Expand Down Expand Up @@ -111,10 +115,49 @@ public Client(String clientId, String clientSecret, String apiHost,
this.userAgent = client.userAgent;
}

/**
* Legacy constructor which allows specifying custom CaCerts.
*
* @param clientId This value is the client id provided by Duo in the admin
* panel.
* @param clientSecret This value is the client secret provided by Duo in the
* admin panel.
* @param apiHost This value is the api host provided by Duo in the admin panel.
* @param redirectUri This value is the uri which Duo should redirect to after
* 2FA is completed.
* @param proxyHost This value is the hostname of the proxy server
* @param proxyPort This value is the port number of the proxy server
* @param userCaCerts This value is a list of CA Certificates used to validate connections to Duo
*
* @throws DuoException For problems building the client
*
* @deprecated The constructors are deprecated. Prefer the
* {@link Client.Builder} for instantiating Clients
*/
@Deprecated
public Client(String clientId, String clientSecret, String apiHost,
String redirectUri, String proxyHost, Integer proxyPort,
String[] userCaCerts) throws DuoException {
Client client = new Builder(clientId, clientSecret, proxyHost, proxyPort, apiHost, redirectUri)
.setCACerts(userCaCerts)
.build();
this.clientId = client.clientId;
this.clientSecret = client.clientSecret;
this.apiHost = client.apiHost;
this.redirectUri = client.redirectUri;
this.useDuoCodeAttribute = client.useDuoCodeAttribute;
this.duoConnector = client.duoConnector;
this.userAgent = client.userAgent;
this.proxyHost = client.proxyHost;
this.proxyPort = client.proxyPort;
}

public static class Builder {
private final String clientId;
private final String clientSecret;
private final String apiHost;
private String proxyHost;
private Integer proxyPort;
private final String redirectUri;
private Boolean useDuoCodeAttribute;
private String[] caCerts;
Expand Down Expand Up @@ -162,6 +205,33 @@ public Builder(String clientId, String clientSecret, String apiHost,
this.userAgent = computeUserAgent();
}

/**
* Builder.
*
* @param clientId This value is the client id provided by Duo in the admin
* panel.
* @param clientSecret This value is the client secret provided by Duo in the
* admin panel.
* @param proxyHost This value is the hostname of the proxy server
* @param proxyPort This value is the port number of the proxy server
* @param apiHost This value is the api host provided by Duo in the admin
* panel.
* @param redirectUri This value is the uri which Duo should redirect to after
* 2FA is completed.
*/
public Builder(String clientId, String clientSecret, String proxyHost,
Integer proxyPort, String apiHost, String redirectUri) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.apiHost = apiHost;
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
this.redirectUri = redirectUri;
this.caCerts = DEFAULT_CA_CERTS;
this.useDuoCodeAttribute = true;
this.userAgent = computeUserAgent();
}

/**
* Build the client object.
*
Expand All @@ -179,7 +249,7 @@ public Client build() throws DuoException {
client.redirectUri = redirectUri;
client.useDuoCodeAttribute = useDuoCodeAttribute;
client.userAgent = userAgent;
client.duoConnector = new DuoConnector(apiHost, caCerts);
client.duoConnector = new DuoConnector(apiHost, proxyHost, proxyPort, caCerts);

return client;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import com.duosecurity.model.HealthCheckResponse;
import com.duosecurity.model.TokenResponse;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import okhttp3.CertificatePinner;
import okhttp3.OkHttpClient;
import retrofit2.Call;
Expand All @@ -22,18 +24,41 @@ public class DuoConnector {
/**
* DuoConnector Constructor.
*
* @param apiHost This value is the api host provided by Duo in the admin panel.
* @param caCerts CA Certificates used to connect to Duo
* @param apiHost This value is the api host provided by Duo in the admin panel.
* @param caCerts CA Certificates used to connect to Duo
*
* @throws DuoException For issues getting and validating the URL
* @throws DuoException For issues getting and validating the URL
*/
public DuoConnector(String apiHost, String[] caCerts) throws DuoException {
CertificatePinner duoCertificatePinner = new CertificatePinner.Builder()
.add(apiHost, caCerts)
.build();
this(apiHost, null, null, caCerts);
}

OkHttpClient client = new OkHttpClient.Builder().certificatePinner(
duoCertificatePinner).build();
eosmith marked this conversation as resolved.
Show resolved Hide resolved
/**
* DuoConnector Constructor.
*
* @param apiHost This value is the api host provided by Duo in the admin panel.
* @param proxyHost This value is the proxy server hostname
* @param proxyPort This value is the proxy server port
* @param caCerts CA Certificates used to connect to Duo
*
* @throws DuoException For issues getting and validating the URL
*/
public DuoConnector(String apiHost, String proxyHost, Integer proxyPort, String[] caCerts)
throws DuoException {
CertificatePinner duoCertificatePinner = new CertificatePinner.Builder()
.add(apiHost, caCerts).build();
OkHttpClient client;
if (proxyHost != null && proxyPort != null) {
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
client = new OkHttpClient.Builder()
.certificatePinner(duoCertificatePinner)
.proxy(proxy)
.build();
} else {
client = new OkHttpClient.Builder()
.certificatePinner(duoCertificatePinner)
.build();
}

retrofit = new Retrofit.Builder()
.baseUrl(getAndValidateUrl(apiHost, "").toString())
Expand All @@ -53,7 +78,7 @@ public DuoConnector(String apiHost, String[] caCerts) throws DuoException {
* @throws DuoException For issues sending or receiving the request
*/
public HealthCheckResponse duoHealthcheck(String clientId, String clientAssertion)
throws DuoException {
throws DuoException {
DuoService service = retrofit.create(DuoService.class);
Call<HealthCheckResponse> callSync = service.duoHealthCheck(clientId, clientAssertion);
try {
Expand Down Expand Up @@ -84,19 +109,14 @@ public TokenResponse exchangeAuthorizationCodeFor2FAResult(String userAgent, Str
String duoCode, String redirectUri,
String clientAssertionType,
String clientAssertion)
throws DuoException {
throws DuoException {
DuoService service = retrofit.create(DuoService.class);
Call<TokenResponse> callSync = service.exchangeAuthorizationCodeFor2FAResult(userAgent,
grantType, duoCode, redirectUri, clientAssertionType, clientAssertion);
try {
Response<TokenResponse> response = callSync.execute();
if (response.code() != SUCCESS_STATUS_CODE || response.body() == null) {
String message = response.message();
if (response.errorBody() != null) {
throw new DuoException(String.format("msg=%s, msg_detail=%s",
message, response.errorBody().string()));
}
throw new DuoException(message);
throw new DuoException(response.message());
eosmith marked this conversation as resolved.
Show resolved Hide resolved
}
return response.body();
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ void exchangeAuthorizationCodeFor2FAResult_error_code() throws IOException, DuoE
"client_assertion_type", "client_assertion");
Assertions.fail();
} catch (DuoException e) {
assertEquals("msg=Response.error(), msg_detail=", e.getMessage());
assertEquals("Response.error()", e.getMessage());
yizshi marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand All @@ -149,7 +149,7 @@ void exchangeAuthorizationCodeFor2FAResult_null_body() throws IOException, DuoEx
Assertions.fail();
} catch (DuoException e) {
// Response.success() is the error message because that's the default message when manually crafting
// a successful (200) response. This still verifies we're properly creating the DuoExpection.
// a successful (200) response. This still verifies we're properly creating the DuoException.
assertEquals("Response.success()", e.getMessage());
}
}
Expand Down
Loading