Skip to content
This repository has been archived by the owner on Jan 12, 2024. It is now read-only.

Commit

Permalink
Add an Okta connector that always requires MFA (#14)
Browse files Browse the repository at this point in the history
* Adds an Okta connector that always requires MFA (regardless of user settings). This allows you to require MFA for Cerberus login, without having to require MFA in Okta for all users.
  • Loading branch information
sdford authored Jan 30, 2017
1 parent 4ad3a94 commit 15ce938
Show file tree
Hide file tree
Showing 8 changed files with 872 additions and 199 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,68 +17,53 @@

package com.nike.cerberus.auth.connector.okta;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.nike.backstopper.exception.ApiException;
import com.nike.cerberus.error.DefaultApiError;
import com.okta.sdk.clients.AuthApiClient;
import com.okta.sdk.clients.FactorsApiClient;
import com.okta.sdk.clients.UserApiClient;
import com.okta.sdk.framework.ApiClientConfiguration;
import com.okta.sdk.models.auth.AuthResult;
import com.okta.sdk.models.factors.Factor;
import com.okta.sdk.models.usergroups.UserGroup;
import org.apache.commons.lang3.text.WordUtils;

import javax.inject.Named;
import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
* Helper methods to authenticate with Okta.
*/
public class OktaAuthHelper {

public static final String AUTHENTICATION_MFA_REQUIRED_STATUS = "MFA_REQUIRED";

public static final String AUTHENTICATION_SUCCESS_STATUS = "SUCCESS";

private static final ImmutableMap<String, String> MFA_FACTOR_NAMES = ImmutableMap.of(
"google", "Google Authenticator",
"okta" , "Okta Verify");

private final ObjectMapper objectMapper;
public class OktaApiClientHelper {

private final AuthApiClient authClient;

private final UserApiClient userApiClient;

public OktaAuthHelper(AuthApiClient authClient,
UserApiClient userApiClient,
ObjectMapper objectMapper) {
private final FactorsApiClient factorsApiClient;

protected OktaApiClientHelper(final AuthApiClient authClient,
final UserApiClient userApiClient,
final FactorsApiClient factorsApiClient) {

this.authClient = authClient;
this.userApiClient = userApiClient;
this.objectMapper = objectMapper;
this.factorsApiClient = factorsApiClient;
}

@Inject
public OktaAuthHelper(@Named("auth.connector.okta.api_key") final String oktaApiKey,
@Named("auth.connector.okta.base_url") final String baseUrl,
final ObjectMapper objectMapper) {
public OktaApiClientHelper(@Named("auth.connector.okta.api_key") final String oktaApiKey,
@Named("auth.connector.okta.base_url") final String baseUrl) {

Preconditions.checkArgument(oktaApiKey != null, "okta api key cannot be null");
Preconditions.checkArgument(baseUrl != null, "okta base url cannot be null");

this.objectMapper = objectMapper;

this.authClient = new AuthApiClient(new ApiClientConfiguration(baseUrl, oktaApiKey));
this.userApiClient = new UserApiClient(new ApiClientConfiguration(baseUrl, oktaApiKey));
final ApiClientConfiguration clientConfiguration = new ApiClientConfiguration(baseUrl, oktaApiKey);
authClient = new AuthApiClient(clientConfiguration);
userApiClient = new UserApiClient(clientConfiguration);
factorsApiClient = new FactorsApiClient(clientConfiguration);
}

/**
Expand All @@ -90,7 +75,7 @@ public OktaAuthHelper(@Named("auth.connector.okta.api_key") final String oktaApi
protected List<UserGroup> getUserGroups(final String userId) {

try {
return this.userApiClient.getUserGroups(userId);
return userApiClient.getUserGroups(userId);
} catch (IOException ioe) {
final String msg = String.format("failed to get user groups for user (%s) for reason: %s", userId,
ioe.getMessage());
Expand All @@ -116,7 +101,7 @@ protected AuthResult verifyFactor(final String factorId,

final AuthResult authResult;
try {
authResult = this.authClient.authenticateWithFactor(stateToken, factorId, passCode);
authResult = authClient.authenticateWithFactor(stateToken, factorId, passCode);
} catch (IOException ioe) {
final String msg = String.format("stateToken: %s failed to verify 2nd factor for reason: %s",
stateToken, ioe.getMessage());
Expand All @@ -142,7 +127,7 @@ protected AuthResult authenticateUser(final String username, final String passwo
final String relayState) {

try {
return this.authClient.authenticate(username, password, relayState);
return authClient.authenticate(username, password, relayState);
} catch (IOException ioe) {
final String msg = String.format("failed to authenticate user (%s) for reason: %s", username,
ioe.getMessage());
Expand All @@ -154,20 +139,16 @@ protected AuthResult authenticateUser(final String username, final String passwo
}

/**
* Convenience method for parsing the Okta response and mapping it to a class.
*
* @param authResult The Okta authentication result object
* @return Deserialized object from the response body
* Get list of enrolled MFA factors for a user
* @param userId Okta user ID
* @return List of factors
*/
protected EmbeddedAuthResponseDataV1 getEmbeddedAuthData(final AuthResult authResult) {

Preconditions.checkArgument(authResult != null, "auth result cannot be null.");
protected List<Factor> getFactorsByUserId(final String userId) {

final Map<String, Object> embedded = authResult.getEmbedded();
Preconditions.checkArgument(userId != null, "user id cannot be null.");

try {
final String embeddedJson = objectMapper.writeValueAsString(embedded);
return objectMapper.readValue(embeddedJson, EmbeddedAuthResponseDataV1.class);
return factorsApiClient.getUserLifecycleFactors(userId);
} catch (IOException e) {
throw ApiException.newBuilder()
.withApiErrors(DefaultApiError.INTERNAL_SERVER_ERROR)
Expand All @@ -177,20 +158,4 @@ protected EmbeddedAuthResponseDataV1 getEmbeddedAuthData(final AuthResult authRe
}
}

/**
* Print a user-friendly name for a MFA device
* @param factor - Okta MFA factor
* @return Device name
*/
protected String getDeviceName(final Factor factor) {

Preconditions.checkArgument(factor != null, "factor cannot be null.");

final String factorProvider = factor.getProvider().toLowerCase();
if (MFA_FACTOR_NAMES.containsKey(factorProvider)) {
return MFA_FACTOR_NAMES.get(factorProvider);
}

return WordUtils.capitalizeFully(factorProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.nike.cerberus.auth.connector.AuthResponse;
import com.nike.cerberus.auth.connector.AuthStatus;
import com.okta.sdk.models.auth.AuthResult;
import com.okta.sdk.models.factors.Factor;
import com.okta.sdk.models.usergroups.UserGroup;
import org.apache.commons.lang3.StringUtils;

Expand All @@ -37,53 +38,64 @@
*/
public class OktaAuthConnector implements AuthConnector {

private final OktaAuthHelper oktaAuthHelper;
private final OktaApiClientHelper oktaApiClientHelper;

private final OktaClientResponseUtils oktaClientResponseUtils;

@Inject
public OktaAuthConnector(final OktaAuthHelper oktaAuthHelper) {
public OktaAuthConnector(final OktaApiClientHelper oktaApiClientHelper,
final OktaClientResponseUtils oktaClientResponseUtils) {

this.oktaAuthHelper = oktaAuthHelper;
this.oktaApiClientHelper = oktaApiClientHelper;
this.oktaClientResponseUtils = oktaClientResponseUtils;
}

@Override
public AuthResponse authenticate(String username, String password) {

final AuthResult authResult = oktaAuthHelper.authenticateUser(username, password, null);
final EmbeddedAuthResponseDataV1 embeddedData = oktaAuthHelper.getEmbeddedAuthData(authResult);
final AuthResult authResult = oktaApiClientHelper.authenticateUser(username, password, null);
final String userId = oktaClientResponseUtils.getUserIdFromAuthResult(authResult);
final String userLogin = oktaClientResponseUtils.getUserLoginFromAuthResult(authResult);

final AuthData authData = new AuthData();
final AuthData authData = new AuthData()
.setUserId(userId)
.setUsername(userLogin);
final AuthResponse authResponse = new AuthResponse().setData(authData);

final List<Factor> factors;
if (StringUtils.equals(authResult.getStatus(), OktaClientResponseUtils.AUTHENTICATION_MFA_REQUIRED_STATUS) ||
StringUtils.equals(authResult.getStatus(), OktaClientResponseUtils.AUTHENTICATION_MFA_ENROLL_STATUS)) {

if (StringUtils.equals(authResult.getStatus(), OktaAuthHelper.AUTHENTICATION_MFA_REQUIRED_STATUS)) {
authResponse.setStatus(AuthStatus.MFA_REQUIRED);
authData.setStateToken(authResult.getStateToken());
authResponse.setStatus(AuthStatus.MFA_REQUIRED);

factors = oktaClientResponseUtils.getUserFactorsFromAuthResult(authResult);
oktaClientResponseUtils.validateUserFactors(factors);

embeddedData.getFactors().forEach(factor -> authData.getDevices().add(new AuthMfaDevice()
factors.forEach(factor -> authData.getDevices().add(new AuthMfaDevice()
.setId(factor.getId())
.setName(oktaAuthHelper.getDeviceName(factor))));
} else {
.setName(oktaClientResponseUtils.getDeviceName(factor))));
}
else {
authResponse.setStatus(AuthStatus.SUCCESS);
}

authData.setUserId(String.valueOf(embeddedData.getUser().getId()));
authData.setUsername(embeddedData.getUser().getProfile().getLogin());

return authResponse;
}

@Override
public AuthResponse mfaCheck(String stateToken, String deviceId, String otpToken) {

final AuthResult userAuthResult = oktaAuthHelper.verifyFactor(deviceId, stateToken, otpToken);
final EmbeddedAuthResponseDataV1 embeddedAuthData = oktaAuthHelper.getEmbeddedAuthData(userAuthResult);

final AuthData authData = new AuthData();
final AuthResponse authResponse = new AuthResponse().setData(authData);
final AuthResult authResult = oktaApiClientHelper.verifyFactor(deviceId, stateToken, otpToken);
final String userId = oktaClientResponseUtils.getUserIdFromAuthResult(authResult);
final String userLogin = oktaClientResponseUtils.getUserLoginFromAuthResult(authResult);

authResponse.setStatus(AuthStatus.SUCCESS);
authData.setUserId(embeddedAuthData.getUser().getId());
authData.setUsername(embeddedAuthData.getUser().getProfile().getLogin());
final AuthData authData = new AuthData()
.setUserId(userId)
.setUsername(userLogin);
final AuthResponse authResponse = new AuthResponse()
.setData(authData)
.setStatus(AuthStatus.SUCCESS);

return authResponse;
}
Expand All @@ -93,7 +105,7 @@ public Set<String> getGroups(AuthData authData) {

Preconditions.checkNotNull(authData, "auth data cannot be null.");

final List<UserGroup> userGroups = oktaAuthHelper.getUserGroups(authData.getUserId());
final List<UserGroup> userGroups = oktaApiClientHelper.getUserGroups(authData.getUserId());

final Set<String> groups = new HashSet<>();
if (userGroups == null) {
Expand Down
Loading

0 comments on commit 15ce938

Please sign in to comment.