diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/filter/AuthorizationRequestImpl.java b/openid-connect-client/src/main/java/org/mitre/oauth2/filter/AuthorizationRequestImpl.java new file mode 100644 index 000000000..39f12f353 --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/filter/AuthorizationRequestImpl.java @@ -0,0 +1,83 @@ +package org.mitre.oauth2.filter; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.provider.AuthorizationRequest; + +public class AuthorizationRequestImpl implements AuthorizationRequest { + + private JsonObject token; + private String clientId; + private Set scopes = null; + + public AuthorizationRequestImpl(JsonObject token) { + this.token = token; + clientId = token.get("client_id").getAsString(); + scopes = new HashSet(); + for (JsonElement e : token.get("scope").getAsJsonArray()) { + scopes.add(e.getAsString()); + } + } + + @Override + public Map getAuthorizationParameters() { + return null; + } + + @Override + public Map getApprovalParameters() { + return null; + } + + @Override + public String getClientId() { + return clientId; + } + + @Override + public Set getScope() { + + return scopes; + } + + @Override + public Set getResourceIds() { + return null; + } + + @Override + public Collection getAuthorities() { + return null; + } + + @Override + public boolean isApproved() { + return true; + } + + @Override + public boolean isDenied() { + return false; + } + + @Override + public String getState() { + return null; + } + + @Override + public String getRedirectUri() { + return null; + } + + @Override + public Set getResponseTypes() { + return null; + } + +} diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/filter/IntrospectingTokenService.java b/openid-connect-client/src/main/java/org/mitre/oauth2/filter/IntrospectingTokenService.java new file mode 100644 index 000000000..f93c52020 --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/filter/IntrospectingTokenService.java @@ -0,0 +1,201 @@ +package org.mitre.oauth2.filter; + +import com.google.common.collect.Sets; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2RefreshToken; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +public class IntrospectingTokenService implements ResourceServerTokenServices { + + + private String clientId; + private String clientSecret; + private String introspectionUrl; + + // Inner class to store in the hash map + private class TokenCacheObject { OAuth2AccessToken token; OAuth2Authentication auth; + private TokenCacheObject(OAuth2AccessToken token, OAuth2Authentication auth) { + this.token = token; + this.auth = auth; + } + } + private Map authCache = new HashMap(); + + public String getIntrospectionUrl() { + return introspectionUrl; + } + + public void setIntrospectionUrl(String introspectionUrl) { + this.introspectionUrl = introspectionUrl; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + // Check if there is a token and authentication in the cache + // and check if it is not expired. + private TokenCacheObject checkCache(String key) { + if(authCache.containsKey(key)) { + TokenCacheObject tco = authCache.get(key); + if (tco.token.getExpiration().after(new Date())) { + return tco; + } else { + // if the token is expired, don't keep things around. + authCache.remove(key); + } + } + return null; + } + + private AuthorizationRequest createAuthRequest(final JsonObject token) { + AuthorizationRequest authReq = new AuthorizationRequestImpl(token); + return authReq; + } + + // create a default authentication object with authority ROLE_API + private Authentication createAuthentication(JsonObject token){ + // TODO: user_id is going to go away. Will have to fix. + return new PreAuthenticatedAuthenticationToken(token.get("user_id").getAsString(), null, AuthorityUtils.createAuthorityList("ROLE_API")); + } + + private OAuth2AccessToken createAccessToken(final JsonObject token, final String tokenString){ + OAuth2AccessToken accessToken = new OAuth2AccessTokenImpl(token, tokenString); + return accessToken; + } + + // Validate a token string against the introspection endpoint, + // then parse it and store it in the local cache. Return true on + // sucess, false otherwise. + private boolean parseToken(String accessToken) { + String validatedToken = null; + // Use the SpringFramework RestTemplate to send the request to the endpoint + + RestTemplate restTemplate = new RestTemplate(); + MultiValueMap form = new LinkedMultiValueMap(); + form.add("token",accessToken); + form.add("client_id", this.clientId); + form.add("client_scret", this.clientSecret); + + try { + validatedToken = restTemplate.postForObject(introspectionUrl, form, String.class); + } catch (RestClientException rce) { + // TODO: LOG THIS!? + LoggerFactory.getLogger(IntrospectingTokenService.class).error("validateToken", rce); + } + if (validatedToken != null) { + // parse the json + JsonElement jsonRoot = new JsonParser().parse(validatedToken); + if (!jsonRoot.isJsonObject()) { + return false; // didn't get a proper JSON object + } + + JsonObject tokenResponse = jsonRoot.getAsJsonObject(); + + if (tokenResponse.get("error") != null) { + // report an error? + return false; + } + + if (!tokenResponse.get("valid").getAsBoolean()){ + // non-valid token + return false; + } + // create an OAuth2Authentication + OAuth2Authentication auth = new OAuth2Authentication(createAuthRequest(tokenResponse), null); + // create an OAuth2AccessToken + OAuth2AccessToken token = createAccessToken(tokenResponse, accessToken); + + if (token.getExpiration().after(new Date())){ + // Store them in the cache + authCache.put(accessToken, new TokenCacheObject(token,auth)); + + return true; + } + } + + // If we never put a token and an authentication in the cache... + return false; + } + + @Override + public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException { + // First check if the in memory cache has an Authentication object, and that it is still valid + // If Valid, return it + TokenCacheObject cacheAuth = checkCache(accessToken); + if (cacheAuth != null) { + return cacheAuth.auth; + } else { + if (parseToken(accessToken)) { + cacheAuth = authCache.get(accessToken); + if (cacheAuth != null && (cacheAuth.token.getExpiration().after(new Date()))) { + return cacheAuth.auth; + } else { + return null; + } + } else { + return null; + } + } + } + + @Override + public OAuth2AccessToken readAccessToken(String accessToken) { + // First check if the in memory cache has a Token object, and that it is still valid + // If Valid, return it + TokenCacheObject cacheAuth = checkCache(accessToken); + if (cacheAuth != null) { + return cacheAuth.token; + } else { + if (parseToken(accessToken)) { + cacheAuth = authCache.get(accessToken); + if (cacheAuth != null && (cacheAuth.token.getExpiration().after(new Date()))) { + return cacheAuth.token; + } else { + return null; + } + } else { + return null; + } + } + } + +} diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/filter/OAuth2AccessTokenImpl.java b/openid-connect-client/src/main/java/org/mitre/oauth2/filter/OAuth2AccessTokenImpl.java new file mode 100644 index 000000000..471b15012 --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/filter/OAuth2AccessTokenImpl.java @@ -0,0 +1,90 @@ +package org.mitre.oauth2.filter; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2RefreshToken; + + +public class OAuth2AccessTokenImpl implements OAuth2AccessToken { + + private JsonObject token; + private String tokenString; + private Set scopes = null; + private Date expireDate; + + + public OAuth2AccessTokenImpl(JsonObject token, String tokenString) { + this.token = token; + this.tokenString = tokenString; + scopes = new HashSet(); + for (JsonElement e : token.get("scope").getAsJsonArray()) { + scopes.add(e.getAsString()); + } + + DateFormat dateFormater = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); + try { + expireDate = dateFormater.parse(token.get("expires_at").getAsString()); + } catch (ParseException ex) { + Logger.getLogger(IntrospectingTokenService.class.getName()).log(Level.SEVERE, null, ex); + } + } + + + @Override + public Map getAdditionalInformation() { + return null; + } + + @Override + public Set getScope() { + return scopes; + } + + @Override + public OAuth2RefreshToken getRefreshToken() { + return null; + } + + @Override + public String getTokenType() { + return BEARER_TYPE; + } + + @Override + public boolean isExpired() { + if (expireDate != null && expireDate.before(new Date())) { + return true; + } + return false; + } + + @Override + public Date getExpiration() { + return expireDate; + } + + @Override + public int getExpiresIn() { + if (expireDate != null) { + return (int)TimeUnit.MILLISECONDS.toSeconds(expireDate.getTime() - (new Date()).getTime()); + } + return 0; + } + + @Override + public String getValue() { + return tokenString; + } + +}