-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This approach depends on the access_token and not our ability to introspect it. The access token contains enough information to create a PIC-SURE user in our application.
- Loading branch information
Showing
3 changed files
with
119 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,12 +4,17 @@ | |
import edu.harvard.dbmi.avillach.util.HttpClientUtil; | ||
import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; | ||
import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; | ||
import edu.harvard.hms.dbmi.avillach.auth.data.entity.Connection; | ||
import edu.harvard.hms.dbmi.avillach.auth.data.entity.Role; | ||
import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; | ||
import edu.harvard.hms.dbmi.avillach.auth.data.repository.ConnectionRepository; | ||
import edu.harvard.hms.dbmi.avillach.auth.data.repository.RoleRepository; | ||
import edu.harvard.hms.dbmi.avillach.auth.data.repository.UserRepository; | ||
import edu.harvard.hms.dbmi.avillach.auth.rest.UserService; | ||
import edu.harvard.hms.dbmi.avillach.auth.utils.AuthUtils; | ||
import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; | ||
import io.jsonwebtoken.Claims; | ||
import io.jsonwebtoken.Jws; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.apache.http.Header; | ||
import org.apache.http.entity.StringEntity; | ||
|
@@ -22,6 +27,9 @@ | |
import javax.ws.rs.core.UriInfo; | ||
import java.util.*; | ||
|
||
/** | ||
* This class handles the authentication process using Okta OAuth authentication. | ||
*/ | ||
public class OktaOAuthAuthenticationService { | ||
|
||
private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||
|
@@ -32,6 +40,9 @@ public class OktaOAuthAuthenticationService { | |
@Inject | ||
private RoleRepository roleRepository; | ||
|
||
@Inject | ||
private ConnectionRepository connectionRepository; | ||
|
||
@Inject | ||
private AuthUtils authUtil; | ||
|
||
|
@@ -47,22 +58,22 @@ public class OktaOAuthAuthenticationService { | |
public Response authenticate(UriInfo uriInfo, Map<String, String> authRequest) { | ||
String code = authRequest.get("code"); | ||
if (StringUtils.isNotBlank(code)) { | ||
|
||
JsonNode userToken = handleCodeTokenExchange(uriInfo, code); | ||
logger.info("UserToken: " + userToken); | ||
JsonNode introspectResponse = introspectToken(userToken); | ||
|
||
if (introspectResponse == null) { | ||
return PICSUREResponse.error("Failed to introspect access token."); | ||
} | ||
|
||
logger.info("Introspection Token: " + introspectResponse); | ||
Jws<Claims> oktaPayloadClaims = oktaAuthorizationTokenToClaims(userToken); | ||
logger.info("oktaPayloadClaims: " + oktaPayloadClaims); | ||
|
||
User user = initializeUser(introspectResponse); | ||
if (user == null) { | ||
logger.info("LOGIN FAILED ___ USER NOT FOUND ___ " + userToken.get("email").asText() + ":" + userToken.get("sub").asText() + " ___"); | ||
return PICSUREResponse.error("User not found"); | ||
Map<String, Object> oktaPayloadClaimsMap = convertOktaPayloadClaimsToMap(oktaPayloadClaims); | ||
if (oktaPayloadClaimsMap == null) { | ||
logger.info("LOGIN FAILED ___ USER FAILED TO AUTHENTICATE ___"); | ||
return PICSUREResponse.error("User not authenticated"); | ||
} | ||
|
||
logger.info("oktaPayloadClaimsMap: " + oktaPayloadClaimsMap); | ||
User user = initializeUser(oktaPayloadClaimsMap); | ||
|
||
HashMap<String, String> responseMap = createUserClaims(user); | ||
logger.info("LOGIN SUCCESS ___ " + user.getEmail() + ":" + user.getUuid().toString() + " ___ Authorization will expire at ___ " + responseMap.get("expirationDate") + "___"); | ||
|
||
|
@@ -73,19 +84,73 @@ public Response authenticate(UriInfo uriInfo, Map<String, String> authRequest) { | |
return PICSUREResponse.error("User not authenticated"); | ||
} | ||
|
||
private User initializeUser(JsonNode introspectResponse) { | ||
boolean isActive = introspectResponse.get("active").asBoolean(); | ||
if (!isActive) { | ||
logger.info("LOGIN FAILED ___ USER IS NOT ACTIVE ___ "); | ||
/** | ||
* Convert the claims from the OKTA payload to a map. | ||
* Example Payload Claims: | ||
* { | ||
* "ver": 1, | ||
* "jti": "AT.N6-700lOHdD0VHMs7r94DR9kHUIGKgLyIh_zk94OFOk", | ||
* "iss": "https://hms-srce.oktapreview.com", | ||
* "aud": "https://hms-srce.oktapreview.com", | ||
* "sub": "[email protected]", | ||
* "iat": 1706049726, | ||
* "exp": 1706053326, | ||
* "cid": "0oacgzw1kdNsPgRw11d7", | ||
* "uid": "00ubtr4ospshhg12r1d7", | ||
* "scp": [ | ||
* "openid" | ||
* ], | ||
* "auth_time": 1706042809 | ||
* } | ||
* | ||
* @param oktaPayloadClaims The claims from the OKTA payload | ||
* @return The claims as a map | ||
*/ | ||
private Map<String, Object> convertOktaPayloadClaimsToMap(Jws<Claims> oktaPayloadClaims) { | ||
if (oktaPayloadClaims == null) { | ||
return null; | ||
} | ||
|
||
Claims body = oktaPayloadClaims.getBody(); | ||
Map<String, Object> oktaPayloadClaimsMap = new HashMap<>(); | ||
oktaPayloadClaimsMap.put("sub", body.get("sub")); | ||
oktaPayloadClaimsMap.put("ver", body.get("ver")); | ||
oktaPayloadClaimsMap.put("jti", body.get("jti")); | ||
oktaPayloadClaimsMap.put("iss", body.get("iss")); | ||
oktaPayloadClaimsMap.put("aud", body.get("aud")); | ||
oktaPayloadClaimsMap.put("iat", body.get("iat")); | ||
oktaPayloadClaimsMap.put("exp", body.get("exp")); | ||
oktaPayloadClaimsMap.put("cid", body.get("cid")); | ||
oktaPayloadClaimsMap.put("uid", body.get("uid")); | ||
oktaPayloadClaimsMap.put("scp", body.get("scp")); | ||
|
||
return oktaPayloadClaimsMap; | ||
} | ||
|
||
|
||
private Jws<Claims> oktaAuthorizationTokenToClaims(JsonNode userToken) { | ||
if (userToken.has("access_token")) { | ||
logger.info("LOGIN FAILED ___ USER FAILED TO AUTHENTICATE ___"); | ||
return null; | ||
} | ||
JsonNode accessToken = userToken.get("access_token"); | ||
return JWTUtil.parseToken(JAXRSConfiguration.spClientSecret, accessToken.asText()); | ||
} | ||
|
||
User user = loadUser(introspectResponse); | ||
private User initializeUser(Map<String, Object> oktaPayloadClaimsMap) { | ||
User user = loadUser(oktaPayloadClaimsMap); | ||
clearCache(user); | ||
user = addUserRoles(user); | ||
addUserRoles(user); | ||
|
||
return user; | ||
} | ||
|
||
/** | ||
* Create the claims for the user. This will be used to create the user session for the UI client. | ||
* | ||
* @param user The user | ||
* @return The claims for the user | ||
*/ | ||
private HashMap<String, String> createUserClaims(User user) { | ||
HashMap<String, Object> claims = new HashMap<>(); | ||
claims.put("name", user.getName()); | ||
|
@@ -95,9 +160,10 @@ private HashMap<String, String> createUserClaims(User user) { | |
} | ||
|
||
|
||
private User addUserRoles(User user) { | ||
private void addUserRoles(User user) { | ||
Role openAccessRole = roleRepository.getUniqueResultByColumn("name", FENCEAuthenticationService.fence_open_access_role_name); | ||
return userRepository.createOpenAccessUser(openAccessRole); | ||
user.setRoles(new HashSet<>(List.of(openAccessRole))); | ||
userRepository.merge(user); | ||
} | ||
|
||
private void clearCache(User user) { | ||
|
@@ -110,19 +176,22 @@ private void clearCache(User user) { | |
* will reject their login attempt. | ||
* Documentation: <a href="https://developer.okta.com/docs/reference/api/oidc/#response-example-success-access-token">response-example-success-access-token</a> | ||
* | ||
* @param introspectResponse The response from the introspect endpoint | ||
* @param oktaPayloadClaimsMap The response from the introspect endpoint | ||
* @return The user | ||
*/ | ||
private User loadUser(JsonNode introspectResponse) { | ||
String email = introspectResponse.get("username").asText(); | ||
// TODO: Load the user from the database. For now, just return a new user so we can test. | ||
private User loadUser(Map<String, Object> oktaPayloadClaimsMap) { | ||
String email = (String) oktaPayloadClaimsMap.get("sub"); | ||
long userId = (long) oktaPayloadClaimsMap.get("uid"); | ||
logger.info("loadUser() - email: " + email + ", userId: " + userId); | ||
|
||
Connection okta = connectionRepository.findConnectionById("OKTA"); | ||
User user = new User(); | ||
user.setSubject(introspectResponse.get("sub").asText()); | ||
user.setSubject("okta|" + userId); | ||
user.setEmail(email); | ||
user.setConnection(null); // TODO: We need to load the connection from the database. | ||
user.setConnection(okta); | ||
user.setAcceptedTOS(new Date()); | ||
user.setGeneralMetadata(introspectResponse.toString()); | ||
user.setActive(introspectResponse.get("active").asBoolean()); | ||
user.setGeneralMetadata(JAXRSConfiguration.objectMapper.convertValue(oktaPayloadClaimsMap, JsonNode.class).toString()); | ||
user.setActive(true); | ||
|
||
return user; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters