Skip to content

Commit

Permalink
- Add ability to toggle off use_duo_code_attribute.
Browse files Browse the repository at this point in the history
- Refactor the Client to use the builder pattern; the constructors are now deprecated.
  • Loading branch information
jeffreyparker committed Feb 9, 2021
1 parent ad71f08 commit ca089b0
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 85 deletions.
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ The Duo Universal Client for Java is available from Duo Security on Maven. Incl
```
See https://mvnrepository.com/artifact/com.duosecurity/duo-universal-sdk/1.0.3 for more details.

This SDK sets the `use_duo_code_attribute` attribute to `true`, which forces the the authorization code will
be returned under the attribute name of `duo_code`. See [this link](https://duo.com/docs/oauthapi) for more info.

# Demo

## Build
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,23 @@ public class LoginController {

private Client duoClient;

// Step 1: Construct the duoClient object upon class creation
/**
* Create and initialize the Duo Client.
*
* @throws DuoException For problems creating the Clients
*/
@PostConstruct
public void initializeDuoClient() throws DuoException {
stateMap = new HashMap<>();
duoClient = new Client(clientId, clientSecret, apiHost, redirectUri);
duoClient = new Client.Builder(clientId, clientSecret, apiHost, redirectUri).build();

/* Example of setting optional fields
duoClient = new Client.Builder(clientId, clientSecret, apiHost, redirectUri)
.setUseDuoCodeAttribute(false)
.setCACerts(customCerts)
.appendUserAgentInfo("custom string")
.build();
*/
}

@RequestMapping(value = "/", method = RequestMethod.GET)
Expand Down
231 changes: 160 additions & 71 deletions duo-universal-sdk/src/main/java/com/duosecurity/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,70 +43,180 @@ public class Client {
// **************************************************
// Fields
// **************************************************
private final String clientId;
private String clientId;

private final String clientSecret;
private String clientSecret;

private final String apiHost;
private String apiHost;

private final String redirectUri;
private String redirectUri;

private Boolean useDuoCodeAttribute;

protected DuoConnector duoConnector;

private String userAgent;

private static final String[] DEFAULT_CA_CERTS = {
//C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Assured ID Root CA
"sha256/I/Lt/z7ekCWanjD0Cvj5EqXls2lOaThEA0H2Bg4BT/o=",
//C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root CA
"sha256/r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=",
//C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
"sha256/WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18=",
//C=US, O=SecureTrust Corporation, CN=SecureTrust CA
"sha256/dykHF2FLJfEpZOvbOLX4PKrcD2w2sHd/iA/G3uHTOcw=",
//C=US, O=SecureTrust Corporation, CN=Secure Global CA
"sha256/JZaQTcTWma4gws703OR/KFk313RkrDcHRvUt6na6DCg=",
//C=US, O=Amazon, CN=Amazon Root CA 1
"sha256/++MBgDH5WGvL9Bcn5Be30cRcL0f5O+NyoXuWtQdX1aI=",
//C=US, O=Amazon, CN=Amazon Root CA 2
"sha256/f0KW/FtqTjs108NpYj42SrGvOB2PpxIVM8nWxjPqJGE=",
//C=US, O=Amazon, CN=Amazon Root CA 3
"sha256/NqvDJlas/GRcYbcWE8S/IceH9cq77kg0jVhZeAPXq8k=",
//C=US, O=Amazon, CN=Amazon Root CA 4
"sha256/9+ze1cZgR9KO1kZrVDxA4HQ6voHRCSVNz4RdTCx4U8U=",
//C=BM, O=QuoVadis Limited, CN=QuoVadis Root CA 2
"sha256/j9ESw8g3DxR9XM06fYZeuN1UB4O6xp/GAIjjdD/zM3g="
};

// **************************************************
// Constructors
// This class uses the "Builder" pattern and should not be directly instantiated.
// Use Client.Builder to generate the Client.
// **************************************************

private Client() {
}

/**
* Main constructor.
*
* @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 userCaCerts This value is a list of CA Certificates used to validate connections to Duo.
*
* @throws DuoException For problems validating the client parameters
* Legacy simple constructor.
* @deprecated The constructors are deprecated.
* Prefer the {@link Client.Builder} for instantiating Clients
*/
@Deprecated
public Client(String clientId, String clientSecret, String apiHost, String redirectUri)
throws DuoException {
this(clientId, clientSecret, apiHost, redirectUri, null);
}

/**
* Legacy constructor which allows specifying custom CaCerts.
* @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[] userCaCerts) throws DuoException {
validateClientParams(clientId, clientSecret, apiHost, redirectUri);
this.clientId = clientId;
this.clientSecret = clientSecret;
this.apiHost = apiHost;
this.redirectUri = redirectUri;
this.userAgent = computeUserAgent();
String[] caCerts = validateCaCert(userCaCerts) ? userCaCerts : DEFAULT_CA_CERTS;
duoConnector = new DuoConnector(apiHost, caCerts);
String redirectUri, String[] userCaCerts) throws DuoException {
Client client = new Builder(clientId, clientSecret, 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;
}

public Client(String clientId, String clientSecret, String apiHost, String redirectUri)
throws DuoException {
this(clientId, clientSecret, apiHost, redirectUri, DEFAULT_CA_CERTS);
public static class Builder {
private final String clientId;
private final String clientSecret;
private final String apiHost;
private final String redirectUri;
private Boolean useDuoCodeAttribute;
private String[] caCerts;
private String userAgent;

private static final String[] DEFAULT_CA_CERTS = {
//C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Assured ID Root CA
"sha256/I/Lt/z7ekCWanjD0Cvj5EqXls2lOaThEA0H2Bg4BT/o=",
//C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root CA
"sha256/r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=",
//C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
"sha256/WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18=",
//C=US, O=SecureTrust Corporation, CN=SecureTrust CA
"sha256/dykHF2FLJfEpZOvbOLX4PKrcD2w2sHd/iA/G3uHTOcw=",
//C=US, O=SecureTrust Corporation, CN=Secure Global CA
"sha256/JZaQTcTWma4gws703OR/KFk313RkrDcHRvUt6na6DCg=",
//C=US, O=Amazon, CN=Amazon Root CA 1
"sha256/++MBgDH5WGvL9Bcn5Be30cRcL0f5O+NyoXuWtQdX1aI=",
//C=US, O=Amazon, CN=Amazon Root CA 2
"sha256/f0KW/FtqTjs108NpYj42SrGvOB2PpxIVM8nWxjPqJGE=",
//C=US, O=Amazon, CN=Amazon Root CA 3
"sha256/NqvDJlas/GRcYbcWE8S/IceH9cq77kg0jVhZeAPXq8k=",
//C=US, O=Amazon, CN=Amazon Root CA 4
"sha256/9+ze1cZgR9KO1kZrVDxA4HQ6voHRCSVNz4RdTCx4U8U=",
//C=BM, O=QuoVadis Limited, CN=QuoVadis Root CA 2
"sha256/j9ESw8g3DxR9XM06fYZeuN1UB4O6xp/GAIjjdD/zM3g="
};

/**
* 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 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.
*
* @throws DuoException For problems validating the client parameters
*/
public Builder(String clientId, String clientSecret, String apiHost,
String redirectUri) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.apiHost = apiHost;
this.redirectUri = redirectUri;
this.caCerts = DEFAULT_CA_CERTS;
this.useDuoCodeAttribute = true;
this.userAgent = computeUserAgent();
}

/**
* Build the client object.
*
* @return {@link Client}
*
* @throws DuoException For problems building the client
*/
public Client build() throws DuoException {
validateClientParams(clientId, clientSecret, apiHost, redirectUri);

Client client = new Client();
client.clientId = clientId;
client.clientSecret = clientSecret;
client.apiHost = apiHost;
client.redirectUri = redirectUri;
client.useDuoCodeAttribute = useDuoCodeAttribute;
client.userAgent = userAgent;
client.duoConnector = new DuoConnector(apiHost, caCerts);

return client;
}

/**
* Optionally use custom CA Certificates when validating connections to Duo.
*
* @param userCaCerts List of CA Certificates to use
*/
public Builder setCACerts(String[] userCaCerts) {
if (validateCaCert(userCaCerts)) {
this.caCerts = userCaCerts;
}
return this;
}

/**
* Optionally toggle the returned authorization parameter to use duo_code vs code.
* Defaults true to use duo_code.
*
* @param useDuoCodeAttribute true/false toggle
*/
public Builder setUseDuoCodeAttribute(boolean useDuoCodeAttribute) {
this.useDuoCodeAttribute = useDuoCodeAttribute;
return this;
}

/**
* Optionally appends string to userAgent.
*
* @param newUserAgent Additional info that will be added to the end of the user agent string
*/
public Builder appendUserAgentInfo(String newUserAgent) {
userAgent = format("%s %s", userAgent, newUserAgent);
return this;
}

private String computeUserAgent() {
String duoAgent = format("%s/%s", USER_AGENT_LIB, USER_AGENT_VERSION);
String javaAgent = format("%s/%s",
System.getProperty("java.vendor"),
System.getProperty("java.version"));
String osAgent = format("%s/%s/%s",
System.getProperty("os.name"),
System.getProperty("os.version"),
System.getProperty("os.arch"));

return format("%s %s %s", duoAgent, javaAgent, osAgent);
}
}

// **************************************************
Expand Down Expand Up @@ -145,7 +255,8 @@ public HealthCheckResponse healthCheck() throws DuoException {
public String createAuthUrl(String username, String state) throws DuoException {
validateUsername(username);
validateState(state);
String request = createJwtForAuthUrl(clientId, clientSecret, redirectUri, state, username);
String request = createJwtForAuthUrl(clientId, clientSecret, redirectUri,
state, username, useDuoCodeAttribute);
String query = format(
"?scope=openid&response_type=code&redirect_uri=%s&client_id=%s&request=%s",
redirectUri, clientId, request);
Expand Down Expand Up @@ -222,26 +333,4 @@ public String generateState() {
return Utils.generateJwtId(36);
}

private String computeUserAgent() {
String duoAgent = format("%s/%s", USER_AGENT_LIB, USER_AGENT_VERSION);
String javaAgent = format("%s/%s",
System.getProperty("java.vendor"),
System.getProperty("java.version"));
String osAgent = format("%s/%s/%s",
System.getProperty("os.name"),
System.getProperty("os.version"),
System.getProperty("os.arch"));

return format("%s %s %s", duoAgent, javaAgent, osAgent);
}

/**
* Appends string to userAgent.
*
* @param newUserAgent Additional info that will be added to the end
* of the user agent string
*/
public void appendUserAgentInfo(String newUserAgent) {
userAgent = format("%s %s", userAgent, newUserAgent);
}
}
5 changes: 3 additions & 2 deletions duo-universal-sdk/src/main/java/com/duosecurity/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ static String createJwt(String clientId, String clientSecret, String aud) throws
}

static String createJwtForAuthUrl(String clientId, String clientSecret, String redirectUri,
String state, String username) throws DuoException {
String state, String username,
Boolean useDuoCodeAttribute) throws DuoException {
Date expiration = new Date();
expiration.setTime(expiration.getTime() + ONE_HOUR_IN_MILLISECONDS);
try {
Expand All @@ -61,7 +62,7 @@ static String createJwtForAuthUrl(String clientId, String clientSecret, String r
.withClaim("state", state)
.withClaim("duo_uname", username)
.withClaim("response_type", "code")
.withClaim("use_duo_code_attribute", "True")
.withClaim("use_duo_code_attribute", useDuoCodeAttribute)
.sign(Algorithm.HMAC512(clientSecret));
} catch (UnsupportedEncodingException e) {
throw new DuoException(e.getMessage(), e);
Expand Down
Loading

0 comments on commit ca089b0

Please sign in to comment.