diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/view/TokenIntrospectionView.java b/openid-connect-server/src/main/java/org/mitre/oauth2/view/TokenIntrospectionView.java index 25032c74c..bcce2667d 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/view/TokenIntrospectionView.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/view/TokenIntrospectionView.java @@ -18,119 +18,39 @@ package org.mitre.oauth2.view; import java.io.IOException; import java.io.Writer; -import java.lang.reflect.Type; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.swing.text.DateFormatter; import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; +import org.mitre.openid.connect.model.UserInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; -import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.web.servlet.view.AbstractView; import com.google.common.base.Joiner; -import com.google.gson.ExclusionStrategy; -import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; @Component("tokenIntrospection") public class TokenIntrospectionView extends AbstractView { private static Logger logger = LoggerFactory.getLogger(TokenIntrospectionView.class); + + private static DateFormatter isoDateFormatter = new DateFormatter(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")); + + private Gson gson = new GsonBuilder().create(); @Override protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) { - Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() { - - @Override - public boolean shouldSkipField(FieldAttributes f) { - /* - if (f.getDeclaringClass().isAssignableFrom(OAuth2AccessTokenEntity.class)) { - // we don't want to serialize the whole object, just the scope and timeout - if (f.getName().equals("scope")) { - return false; - } else if (f.getName().equals("expiration")) { - return false; - } else { - // skip everything else on this class - return true; - } - } else { - // serialize other classes without filter (lists and sets and things) - return false; - } - */ - return false; - } - - @Override - public boolean shouldSkipClass(Class clazz) { - // skip the JPA binding wrapper - if (clazz.equals(BeanPropertyBindingResult.class)) { - return true; - } else { - return false; - } - } - - }) - .registerTypeAdapter(OAuth2AccessTokenEntity.class, new JsonSerializer() { - @Override - public JsonElement serialize(OAuth2AccessTokenEntity src, Type typeOfSrc, JsonSerializationContext context) { - JsonObject token = new JsonObject(); - - token.addProperty("active", true); - - token.addProperty("scope", Joiner.on(" ").join(src.getScope())); - - token.add("exp", context.serialize(src.getExpiration())); - - //token.addProperty("audience", src.getAuthenticationHolder().getAuthentication().getAuthorizationRequest().getClientId()); - - token.addProperty("sub", src.getAuthenticationHolder().getAuthentication().getName()); - - token.addProperty("client_id", src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getClientId()); - - token.addProperty("token_type", src.getTokenType()); - - return token; - } - - }) - .registerTypeAdapter(OAuth2RefreshTokenEntity.class, new JsonSerializer() { - @Override - public JsonElement serialize(OAuth2RefreshTokenEntity src, Type typeOfSrc, JsonSerializationContext context) { - JsonObject token = new JsonObject(); - - token.addProperty("active", true); - - token.addProperty("scope", Joiner.on(" ").join(src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getScope())); - - token.add("exp", context.serialize(src.getExpiration())); - - //token.addProperty("audience", src.getAuthenticationHolder().getAuthentication().getAuthorizationRequest().getClientId()); - - token.addProperty("sub", src.getAuthenticationHolder().getAuthentication().getName()); - - token.addProperty("client_id", src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getClientId()); - - return token; - } - - }) - .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") - .create(); - response.setContentType("application/json"); Writer out; @@ -138,13 +58,16 @@ public class TokenIntrospectionView extends AbstractView { try { out = response.getWriter(); - Object obj = model.get("entity"); - if (obj == null) { - obj = model; + UserInfo user = (UserInfo)model.get("user"); + Object obj = model.get("token"); + if (obj instanceof OAuth2AccessTokenEntity) { + gson.toJson(renderAccessToken((OAuth2AccessTokenEntity)obj, user), out); + } else if (obj instanceof OAuth2RefreshTokenEntity) { + gson.toJson(renderRefreshToken((OAuth2RefreshTokenEntity)obj, user), out); + } else { + throw new IOException("Couldn't find a valid entity to render"); } - gson.toJson(obj, out); - } catch (IOException e) { logger.error("IOException occurred in TokenIntrospectionView.java: ", e); @@ -153,4 +76,66 @@ public class TokenIntrospectionView extends AbstractView { } + private JsonObject renderAccessToken(OAuth2AccessTokenEntity src, UserInfo user) { + JsonObject token = new JsonObject(); + + token.addProperty("active", true); + + token.addProperty("scope", Joiner.on(" ").join(src.getScope())); + + if (src.getExpiration() != null) { + try { + token.addProperty("exp", isoDateFormatter.valueToString(src.getExpiration())); + } catch (ParseException e) { + logger.error("Problem formatting expiration date: " + src.getExpiration(), e); + } + } + + if (user != null) { + // if we have a UserInfo, use that for the subject + token.addProperty("sub", user.getSub()); + token.addProperty("user_id", src.getAuthenticationHolder().getAuthentication().getName()); + } else { + // otherwise, use the authentication's username + token.addProperty("sub", src.getAuthenticationHolder().getAuthentication().getName()); + token.addProperty("user_id", src.getAuthenticationHolder().getAuthentication().getName()); + } + + token.addProperty("client_id", src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getClientId()); + + token.addProperty("token_type", src.getTokenType()); + + return token; + } + + private JsonObject renderRefreshToken(OAuth2RefreshTokenEntity src, UserInfo user) { + JsonObject token = new JsonObject(); + + token.addProperty("active", true); + + token.addProperty("scope", Joiner.on(" ").join(src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getScope())); + + if (src.getExpiration() != null) { + try { + token.addProperty("exp", isoDateFormatter.valueToString(src.getExpiration())); + } catch (ParseException e) { + logger.error("Problem formatting expiration date: " + src.getExpiration(), e); + } + } + + if (user != null) { + // if we have a UserInfo, use that for the subject + token.addProperty("sub", user.getSub()); + token.addProperty("user_id", src.getAuthenticationHolder().getAuthentication().getName()); + } else { + // otherwise, use the authentication's username + token.addProperty("sub", src.getAuthenticationHolder().getAuthentication().getName()); + token.addProperty("user_id", src.getAuthenticationHolder().getAuthentication().getName()); + } + + token.addProperty("client_id", src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getClientId()); + + return token; + } + } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java b/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java index 049fc9c37..14923e525 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java @@ -26,6 +26,8 @@ import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.oauth2.service.IntrospectionAuthorizer; import org.mitre.oauth2.service.OAuth2TokenEntityService; +import org.mitre.openid.connect.model.UserInfo; +import org.mitre.openid.connect.service.UserInfoService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -51,6 +53,9 @@ public class IntrospectionEndpoint { @Autowired private IntrospectionAuthorizer introspectionAuthorizer; + + @Autowired + private UserInfoService userInfoService; private static Logger logger = LoggerFactory.getLogger(IntrospectionEndpoint.class); @@ -76,10 +81,14 @@ public class IntrospectionEndpoint { return "jsonEntityView"; } + // clientID is the principal name in the authentication + String clientId = p.getName(); + ClientDetailsEntity authClient = clientService.loadClientByClientId(clientId); ClientDetailsEntity tokenClient = null; Set scopes = null; Object token = null; + UserInfo user = null; try { @@ -91,6 +100,8 @@ public class IntrospectionEndpoint { token = access; + user = userInfoService.getByUsernameAndClientId(access.getAuthenticationHolder().getAuthentication().getName(), tokenClient.getClientId()); + } catch (InvalidTokenException e) { logger.error("Verify failed; Invalid access token. Checking refresh token.", e); try { @@ -101,6 +112,8 @@ public class IntrospectionEndpoint { tokenClient = refresh.getClient(); scopes = refresh.getAuthenticationHolder().getAuthentication().getOAuth2Request().getScope(); + user = userInfoService.getByUsernameAndClientId(refresh.getAuthenticationHolder().getAuthentication().getName(), tokenClient.getClientId()); + token = refresh; } catch (InvalidTokenException e2) { @@ -111,15 +124,12 @@ public class IntrospectionEndpoint { } } - // clientID is the principal name in the authentication - String clientId = p.getName(); - ClientDetailsEntity authClient = clientService.loadClientByClientId(clientId); - if (tokenClient != null && authClient != null) { if (authClient.isAllowIntrospection()) { if (introspectionAuthorizer.isIntrospectionPermitted(authClient, tokenClient, scopes)) { // if it's a valid token, we'll print out information on it - model.addAttribute("entity", token); + model.addAttribute("token", token); + model.addAttribute("user", user); return "tokenIntrospection"; } else { logger.error("Verify failed; client configuration or scope don't permit token introspection");