Skip to content

Commit

Permalink
✅ Add unit tests
Browse files Browse the repository at this point in the history
 - Cover OKTA_MFA_CHOICE cases and failures

 - Separate user interaction effects from code under test (somewhat)

 - Trivial happy path coverage for more of the changed code
  • Loading branch information
AlainODea committed Jan 8, 2019
1 parent cb68001 commit 8685099
Show file tree
Hide file tree
Showing 20 changed files with 802 additions and 262 deletions.
18 changes: 13 additions & 5 deletions src/main/java/com/okta/tools/ListRoles.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@
package com.okta.tools;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.okta.tools.authentication.OktaAuthentication;
import com.okta.tools.authentication.OktaMFA;
import com.okta.tools.authentication.*;
import com.okta.tools.helpers.CookieHelper;
import com.okta.tools.helpers.MenuHelper;
import com.okta.tools.helpers.MenuHelperImpl;
import com.okta.tools.helpers.RoleHelper;
import com.okta.tools.models.AccountOption;
import com.okta.tools.saml.OktaAppClient;
import com.okta.tools.saml.OktaAppClientImpl;
import com.okta.tools.saml.OktaSaml;

import java.util.List;
Expand All @@ -29,9 +32,14 @@ public class ListRoles {
public static void main(String[] args) throws Exception {
OktaAwsCliEnvironment environment = OktaAwsConfig.loadEnvironment();
CookieHelper cookieHelper = new CookieHelper(environment);
OktaMFA oktaMFA = new OktaMFA(environment);
OktaAuthentication oktaAuthentication = new OktaAuthentication(environment, oktaMFA);
OktaSaml oktaSaml = new OktaSaml(environment, cookieHelper, oktaAuthentication);
MenuHelper menuHelper = new MenuHelperImpl();
OktaFactorSelector factorSelector = new OktaFactorSelectorImpl(environment, menuHelper);
OktaMFA oktaMFA = new OktaMFA(factorSelector);
UserConsole userConsole = new UserConsoleImpl();
OktaAuthnClient oktaAuthnClient = new OktaAuthnClientImpl();
OktaAuthentication oktaAuthentication = new OktaAuthentication(environment, oktaMFA, userConsole, oktaAuthnClient);
OktaAppClient oktaAppClient = new OktaAppClientImpl(cookieHelper);
OktaSaml oktaSaml = new OktaSaml(environment, oktaAuthentication, oktaAppClient);
String samlResponse = oktaSaml.getSamlResponse();
RoleHelper roleHelper = new RoleHelper(environment);
List<AccountOption> availableRoles = roleHelper.getAvailableRoles(samlResponse);
Expand Down
18 changes: 12 additions & 6 deletions src/main/java/com/okta/tools/OktaAwsCliAssumeRole.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
import java.util.Optional;

import com.amazonaws.services.securitytoken.model.Credentials;
import com.okta.tools.authentication.OktaAuthentication;
import com.okta.tools.authentication.OktaMFA;
import com.okta.tools.authentication.*;
import com.okta.tools.helpers.*;
import com.okta.tools.saml.OktaAppClient;
import com.okta.tools.saml.OktaAppClientImpl;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand Down Expand Up @@ -64,10 +65,15 @@ private void init() throws Exception {
roleHelper = new RoleHelper(environment);
credentialsHelper = new CredentialsHelper(environment);
profileHelper = new ProfileHelper(credentialsHelper, environment);
OktaMFA oktaMFA = new OktaMFA(environment);
OktaAuthentication oktaAuthentication = new OktaAuthentication(environment, oktaMFA);

oktaSaml = new OktaSaml(environment, cookieHelper, oktaAuthentication);
MenuHelper menuHelper = new MenuHelperImpl();
OktaFactorSelector factorSelector = new OktaFactorSelectorImpl(environment, menuHelper);
OktaMFA oktaMFA = new OktaMFA(factorSelector);
UserConsole userConsole = new UserConsoleImpl();
OktaAuthnClient oktaAuthnClient = new OktaAuthnClientImpl();
OktaAuthentication oktaAuthentication = new OktaAuthentication(environment, oktaMFA, userConsole, oktaAuthnClient);
OktaAppClient oktaAppClient = new OktaAppClientImpl(cookieHelper);

oktaSaml = new OktaSaml(environment, oktaAuthentication, oktaAppClient);

currentSession = sessionHelper.getCurrentSession();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,28 @@
package com.okta.tools.authentication;

import com.okta.tools.OktaAwsCliEnvironment;
import com.okta.tools.helpers.HttpHelper;
import com.okta.tools.models.AuthResult;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.stream.Stream;

public final class OktaAuthentication {
private static final Logger logger = LogManager.getLogger(OktaAuthentication.class);

private final OktaAwsCliEnvironment environment;
private final OktaMFA oktaMFA;
private final UserConsole userConsole;
private final OktaAuthnClient oktaAuthnClient;

public OktaAuthentication(OktaAwsCliEnvironment environment, OktaMFA oktaMFA) {
public OktaAuthentication(OktaAwsCliEnvironment environment, OktaMFA oktaMFA, UserConsole userConsole, OktaAuthnClient oktaAuthnClient) {
this.environment = environment;
this.oktaMFA = oktaMFA;
this.userConsole = userConsole;
this.oktaAuthnClient = oktaAuthnClient;
}

/**
Expand Down Expand Up @@ -129,7 +125,7 @@ public String getOktaSessionToken() throws IOException {
*/
private String getPrimaryAuthResponse(String oktaOrg) throws IOException {
while (true) {
AuthResult response = primaryAuthentication(getUsername(), getPassword(), oktaOrg);
AuthResult response = oktaAuthnClient.primaryAuthentication(getUsername(), getPassword(), oktaOrg);
int requestStatus = response.statusLine.getStatusCode();
primaryAuthFailureHandler(requestStatus, oktaOrg);
if (requestStatus == HttpStatus.SC_OK) {
Expand All @@ -141,42 +137,6 @@ private String getPrimaryAuthResponse(String oktaOrg) throws IOException {
}
}

/**
* Perform primary authentication against Okta
*
* @param username The username of the user
* @param password The password of the user
* @param oktaOrg The org to perform auth against
* @return The authentication result
* @throws IOException If an error occurs during the api call or during the processing of the result.
*/
private AuthResult primaryAuthentication(String username, String password, String oktaOrg) throws IOException {
// Okta authn API docs: https://developer.okta.com/docs/api/resources/authn#primary-authentication
HttpPost httpPost = new HttpPost("https://" + oktaOrg + "/api/v1/authn");

httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-Type", "application/json");
httpPost.addHeader("Cache-Control", "no-cache");

JSONObject authnRequest = new JSONObject();
authnRequest.put("username", username);
authnRequest.put("password", password);

StringEntity entity = new StringEntity(authnRequest.toString(), StandardCharsets.UTF_8);
entity.setContentType("application/json");
httpPost.setEntity(entity);

logger.debug("Calling okta authn service at " + httpPost.getURI());
try (CloseableHttpClient httpClient = HttpHelper.createClient()) {
CloseableHttpResponse authnResponse = httpClient.execute(httpPost);

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(65536);
authnResponse.getEntity().writeTo(byteArrayOutputStream);

return new AuthResult(authnResponse.getStatusLine(), byteArrayOutputStream.toString());
}
}

/**
* Handles failures during the primary authentication flow
*
Expand All @@ -196,8 +156,7 @@ private void primaryAuthFailureHandler(int responseStatus, String oktaOrg) {

private String getUsername() {
if (environment.oktaUsername == null || environment.oktaUsername.isEmpty()) {
System.err.print("Username: ");
return new Scanner(System.in).next();
return userConsole.promptForUsername();
} else {
System.err.println("Username: " + environment.oktaUsername);
return environment.oktaUsername;
Expand All @@ -206,21 +165,12 @@ private String getUsername() {

private String getPassword() {
if (environment.oktaPassword == null) {
return promptForPassword();
return userConsole.promptForPassword();
} else {
return environment.oktaPassword.get();
}
}

private String promptForPassword() {
if (System.console() == null) { // hack to be able to debug in an IDE
System.err.print("Password: ");
return new Scanner(System.in).nextLine();
} else {
return new String(System.console().readPassword("Password: "));
}
}

static RuntimeException makeException(JSONObject primaryAuthResult, String template, Object... args) {
return makeException(primaryAuthResult, null, template, args);
}
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/com/okta/tools/authentication/OktaAuthnClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2019 Okta
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http:https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.okta.tools.authentication;

import com.okta.tools.models.AuthResult;

import java.io.IOException;

public interface OktaAuthnClient {
/**
* Perform primary authentication against Okta
*
* @param username The username of the user
* @param password The password of the user
* @param oktaOrg The org to perform auth against
* @return The authentication result
* @throws IOException If an error occurs during the api call or during the processing of the result.
*/
AuthResult primaryAuthentication(String username, String password, String oktaOrg) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2019 Okta
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http:https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.okta.tools.authentication;

import com.okta.tools.helpers.HttpHelper;
import com.okta.tools.models.AuthResult;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class OktaAuthnClientImpl implements OktaAuthnClient {
private static final Logger logger = LogManager.getLogger(OktaAuthnClientImpl.class);

@Override
public AuthResult primaryAuthentication(String username, String password, String oktaOrg) throws IOException {
// Okta authn API docs: https://developer.okta.com/docs/api/resources/authn#primary-authentication
HttpPost httpPost = new HttpPost("https://" + oktaOrg + "/api/v1/authn");

httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-Type", "application/json");
httpPost.addHeader("Cache-Control", "no-cache");

JSONObject authnRequest = new JSONObject();
authnRequest.put("username", username);
authnRequest.put("password", password);

StringEntity entity = new StringEntity(authnRequest.toString(), StandardCharsets.UTF_8);
entity.setContentType("application/json");
httpPost.setEntity(entity);

logger.debug("Calling okta authn service at " + httpPost.getURI());
try (CloseableHttpClient httpClient = HttpHelper.createClient()) {
CloseableHttpResponse authnResponse = httpClient.execute(httpPost);

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(65536);
authnResponse.getEntity().writeTo(byteArrayOutputStream);

return new AuthResult(authnResponse.getStatusLine(), byteArrayOutputStream.toString());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2019 Okta
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http:https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.okta.tools.authentication;

import org.json.JSONException;
import org.json.JSONObject;

public interface OktaFactorSelector {
/**
* Handles selection of a factor from multiple choices
*
* @param primaryAuthResponse The response from Primary Authentication
* @return A {@link JSONObject} representing the selected factor.
* @throws JSONException if a network or protocol error occurs
*/
JSONObject selectFactor(JSONObject primaryAuthResponse) throws JSONException;
}
Loading

0 comments on commit 8685099

Please sign in to comment.