Skip to content

Commit

Permalink
fixed access token delegation in the GenericOAuth2Filter and added to…
Browse files Browse the repository at this point in the history
…ken payload delegation to clients
  • Loading branch information
albogdano committed Jun 22, 2019
1 parent e5e50a5 commit e6b8bbc
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 33 deletions.
57 changes: 57 additions & 0 deletions para-core/src/main/java/com/erudika/para/core/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ public class User implements ParaObject {
@Stored private String picture;
@Stored private String lastIp;
@Stored @Locked private String tokenSecret;
@Stored private String idpAccessToken;
@Stored private String idpRefreshToken;
@Stored private String idpAccessTokenPayload;

private transient String password;

Expand Down Expand Up @@ -534,6 +537,60 @@ public void setPassword(String password) {
this.password = password;
}

/**
* Used for storing the access token from an OpenID Connect/OAuth 2.0 identity provider.
* @return a JWT access token (JWT is always assumed to be the format)
*/
@JsonIgnore
public String getIdpAccessToken() {
return idpAccessToken;
}

/**
* Sets the IDP access token.
* @param idpAccessToken a token
*/
public void setIdpAccessToken(String idpAccessToken) {
if (!StringUtils.isBlank(idpAccessToken)) {
setIdpAccessTokenPayload(StringUtils.substringBetween(idpAccessToken, "."));
}
this.idpAccessToken = idpAccessToken;
}

/**
* Stores the refresh token from the identity provider.
* @return a JWT refresh token
*/
@JsonIgnore
public String getIdpRefreshToken() {
return idpRefreshToken;
}

/**
* Sets the refresh token.
* @param idpRefreshToken a refresh token
*/
public void setIdpRefreshToken(String idpRefreshToken) {
this.idpRefreshToken = idpRefreshToken;
}

/**
* Returns the JWT payload for the access token coming from the IDP.
* Used for delegating user attributes data to clients.
* @return the payload part in Base64
*/
public String getIdpAccessTokenPayload() {
return idpAccessTokenPayload;
}

/**
* Sets the access token payload.
* @param idpAccessTokenPayload Base64 encoded payload
*/
public void setIdpAccessTokenPayload(String idpAccessTokenPayload) {
this.idpAccessTokenPayload = idpAccessTokenPayload;
}

/**
* Returns a user object for a given identifier.
* @param u a user having a valid identifier set.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ private void validateDelegatedTokenIfNecessary(JWTAuthentication jwt) throws Aut
// - if token delegation is enabled AND
// - if the generic OAuth 2 filter is used
if ("oauth2".equalsIgnoreCase(identityProvider) && oauth2Auth.isAccessTokenDelegationEnabled(app) &&
!oauth2Auth.isValidAccessToken(app, user.getPassword())) {
!oauth2Auth.isValidAccessToken(app, user)) {
logger.debug("The access token delegated from '" + identityProvider + "' is invalid for " +
user.getAppid() + "/" + user.getId());
throw new AuthenticationServiceException("The access token delegated from '" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public class GenericOAuth2Filter extends AbstractAuthenticationProcessingFilter
private final ObjectReader jreader;
private static final String PAYLOAD = "code={0}&redirect_uri={1}"
+ "&scope={2}&client_id={3}&client_secret={4}&grant_type=authorization_code";
private static final String REFRESH_PAYLOAD = "refresh_token={0}"
+ "&scope={1}&client_id={2}&client_secret={3}&grant_type=refresh_token";

/**
* The default filter mapping.
Expand Down Expand Up @@ -101,30 +103,12 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ
String authCode = request.getParameter("code");
if (!StringUtils.isBlank(authCode)) {
String appid = SecurityUtils.getAppidFromAuthRequest(request);
String redirectURI = SecurityUtils.getRedirectUrl(request);
App app = Para.getDAO().read(App.id(appid == null ? Config.getRootAppIdentifier() : appid));
String[] keys = SecurityUtils.getOAuthKeysForApp(app, Config.OAUTH2_PREFIX);
String entity = Utils.formatMessage(PAYLOAD,
authCode, Utils.urlEncode(redirectURI),
URLEncoder.encode(SecurityUtils.getSettingForApp(app, "security.oauth.scope", ""), "UTF-8"),
keys[0], keys[1]);

String acceptHeader = SecurityUtils.getSettingForApp(app, "security.oauth.accept_header", "");
HttpPost tokenPost = new HttpPost(SecurityUtils.getSettingForApp(app, "security.oauth.token_url", ""));
tokenPost.setHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded");
tokenPost.setEntity(new StringEntity(entity, "UTF-8"));
if (!StringUtils.isBlank(acceptHeader)) {
tokenPost.setHeader(HttpHeaders.ACCEPT, acceptHeader);
}

try (CloseableHttpResponse resp1 = httpclient.execute(tokenPost)) {
if (resp1 != null && resp1.getEntity() != null) {
Map<String, Object> token = jreader.readValue(resp1.getEntity().getContent());
if (token != null && token.containsKey("access_token")) {
userAuth = getOrCreateUser(app, (String) token.get("access_token"));
}
EntityUtils.consumeQuietly(resp1.getEntity());
}
Map<String, Object> token = tokenRequest(app, authCode, SecurityUtils.getRedirectUrl(request));
if (token != null && token.containsKey("access_token")) {
userAuth = getOrCreateUser(app, token.get("access_token") +
Config.SEPARATOR + token.get("refresh_token"));
}
}
}
Expand All @@ -143,6 +127,13 @@ public UserAuthentication getOrCreateUser(App app, String accessToken) throws IO
UserAuthentication userAuth = null;
User user = new User();
if (accessToken != null) {
String[] tokens = accessToken.split(Config.SEPARATOR);
String refreshToken = null;
if (tokens.length > 1) {
accessToken = tokens[0];
refreshToken = tokens[1];
}

boolean tokenDelegationEnabled = isAccessTokenDelegationEnabled(app);
Map<String, Object> profile = fetchProfileFromIDP(app, accessToken);

Expand All @@ -169,15 +160,19 @@ public UserAuthentication getOrCreateUser(App app, String accessToken) throws IO
user.setAppid(getAppid(app));
user.setEmail(StringUtils.isBlank(email) ? Utils.getNewId() + "@" + emailDomain : email);
user.setName(StringUtils.isBlank(name) ? "No Name" : name);
user.setPassword(tokenDelegationEnabled ? accessToken : Utils.generateSecurityToken());
user.setPassword(Utils.generateSecurityToken());
if (tokenDelegationEnabled) {
user.setIdpAccessToken(accessToken);
user.setIdpRefreshToken(refreshToken);
}
user.setPicture(getPicture(pic));
user.setIdentifier(Config.OAUTH2_PREFIX.concat(oauthAccountId));
String id = user.create();
if (id == null) {
throw new AuthenticationServiceException("Authentication failed: cannot create new user.");
}
} else {
if (updateUserInfo(user, pic, email, name, accessToken, tokenDelegationEnabled)) {
if (updateUserInfo(user, pic, email, name, accessToken, refreshToken, tokenDelegationEnabled)) {
user.update();
}
}
Expand All @@ -187,8 +182,8 @@ public UserAuthentication getOrCreateUser(App app, String accessToken) throws IO
return SecurityUtils.checkIfActive(userAuth, user, false);
}

private boolean updateUserInfo(User user, String pic, String email, String name, String accessToken,
boolean tokenDelegationEnabled) {
private boolean updateUserInfo(User user, String pic, String email, String name,
String accessToken, String refreshToken, boolean tokenDelegationEnabled) {
String picture = getPicture(pic);
boolean update = false;
if (!StringUtils.equals(user.getPicture(), picture)) {
Expand All @@ -204,7 +199,8 @@ private boolean updateUserInfo(User user, String pic, String email, String name,
update = true;
}
if (tokenDelegationEnabled) {
user.setPassword(accessToken);
user.setIdpAccessToken(accessToken);
user.setIdpRefreshToken(refreshToken);
update = true;
}
return update;
Expand All @@ -223,12 +219,16 @@ public boolean isAccessTokenDelegationEnabled(App app) {
/**
* Validates the access token against the IDP server.
* @param app an app object
* @param accessToken access token
* @param user the user object holding the tokens
* @return true if access token is valid
*/
public boolean isValidAccessToken(App app, String accessToken) {
public boolean isValidAccessToken(App app, User user) {
try {
Map<String, Object> profile = fetchProfileFromIDP(app, accessToken);
Map<String, Object> profile = fetchProfileFromIDP(app, user.getIdpAccessToken());
if (profile == null && user.getIdpRefreshToken() != null) {
refreshTokens(app, user);
profile = fetchProfileFromIDP(app, user.getIdpAccessToken());
}
return profile != null && profile.containsKey(SecurityUtils.getSettingForApp(app,
"security.oauth.parameters.id", "sub"));
} catch (Exception e) {
Expand Down Expand Up @@ -256,14 +256,58 @@ private Map<String, Object> fetchProfileFromIDP(App app, String accessToken) thr

try (CloseableHttpResponse resp2 = httpclient.execute(profileGet)) {
HttpEntity respEntity = resp2.getEntity();
if (respEntity != null) {
if (respEntity != null && resp2.getStatusLine().getStatusCode() == HttpServletResponse.SC_OK) {
profile = jreader.readValue(respEntity.getContent());
EntityUtils.consumeQuietly(respEntity);
}
EntityUtils.consumeQuietly(respEntity);
}
return profile;
}

private void refreshTokens(App app, User user) throws IOException {
Map<String, Object> token = tokenRequest(app, user.getIdpRefreshToken(), null);
if (token != null && token.containsKey("access_token")) {
user.setIdpAccessToken((String) token.get("access_token"));
String newRefresh = (String) token.get("refresh_token");
if (!StringUtils.equals(newRefresh, user.getIdpRefreshToken())) {
user.setIdpRefreshToken(newRefresh);
}
user.update();
}
}

private Map<String, Object> tokenRequest(App app, String authCodeOrRefreshToken, String redirectURI) throws IOException {
String[] keys = SecurityUtils.getOAuthKeysForApp(app, Config.OAUTH2_PREFIX);

String entity;
if (redirectURI == null) {
entity = Utils.formatMessage(REFRESH_PAYLOAD, authCodeOrRefreshToken,
URLEncoder.encode(SecurityUtils.getSettingForApp(app, "security.oauth.scope", ""), "UTF-8"),
keys[0], keys[1]);
} else {
entity = Utils.formatMessage(PAYLOAD, authCodeOrRefreshToken, Utils.urlEncode(redirectURI),
URLEncoder.encode(SecurityUtils.getSettingForApp(app, "security.oauth.scope", ""), "UTF-8"),
keys[0], keys[1]);
}

String acceptHeader = SecurityUtils.getSettingForApp(app, "security.oauth.accept_header", "");
HttpPost tokenPost = new HttpPost(SecurityUtils.getSettingForApp(app, "security.oauth.token_url", ""));
tokenPost.setHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded");
tokenPost.setEntity(new StringEntity(entity, "UTF-8"));
if (!StringUtils.isBlank(acceptHeader)) {
tokenPost.setHeader(HttpHeaders.ACCEPT, acceptHeader);
}

Map<String, Object> tokens = null;
try (CloseableHttpResponse resp1 = httpclient.execute(tokenPost)) {
if (resp1 != null && resp1.getEntity() != null) {
tokens = jreader.readValue(resp1.getEntity().getContent());
EntityUtils.consumeQuietly(resp1.getEntity());
}
}
return tokens;
}

private static String getPicture(String pic) {
if (pic != null) {
if (pic.contains("?")) {
Expand Down

0 comments on commit e6b8bbc

Please sign in to comment.