regex-based webfinger parser (yuck)
parent
70cac0c690
commit
814c70c9a2
|
@ -6,10 +6,13 @@ package org.mitre.openid.connect.client.service.impl;
|
|||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.mitre.openid.connect.client.model.IssuerServiceResponse;
|
||||
import org.mitre.openid.connect.client.service.IssuerService;
|
||||
|
@ -23,6 +26,10 @@ import com.google.common.base.Strings;
|
|||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
/**
|
||||
* Use Webfinger to discover the appropriate issuer for a user-given input string.
|
||||
|
@ -33,11 +40,15 @@ public class WebfingerIssuerService implements IssuerService {
|
|||
|
||||
private static Logger logger = LoggerFactory.getLogger(WebfingerIssuerService.class);
|
||||
|
||||
// map of user input -> issuer, loaded dynamically from webfinger discover
|
||||
private LoadingCache<URI, String> issuers;
|
||||
// pattern used to parse user input; we can't use the built-in java URI parser
|
||||
private static final Pattern pattern = Pattern.compile("(https://|acct:|http://|mailto:)?(([^@]+)@)?([^\\?]+)(\\?([^#]+))?(#(.*))?");
|
||||
|
||||
// map of user input -> issuer, loaded dynamically from webfinger discover
|
||||
private LoadingCache<NormalizedURI, String> issuers;
|
||||
|
||||
private String parameterName = "identifier";
|
||||
|
||||
private String parameterName = "identifier";
|
||||
|
||||
public WebfingerIssuerService() {
|
||||
issuers = CacheBuilder.newBuilder().build(new WebfingerIssuerFetcher());
|
||||
}
|
||||
|
@ -67,53 +78,52 @@ public class WebfingerIssuerService implements IssuerService {
|
|||
/**
|
||||
* Normalize the resource string as per OIDC Discovery.
|
||||
* @param resource
|
||||
* @return
|
||||
* @return the normalized string, or null if the string can't be normalized
|
||||
*/
|
||||
public static URI normalizeResource(String resource) {
|
||||
// try to parse the URI
|
||||
private NormalizedURI normalizeResource(String resource) {
|
||||
// try to parse the URI
|
||||
// NOTE: we can't use the Java built-in URI class because it doesn't split the parts appropriately
|
||||
|
||||
if (Strings.isNullOrEmpty(resource)) {
|
||||
logger.warn("Can't normalize null or empty URI: " + resource);
|
||||
return null; // nothing we can do
|
||||
} else {
|
||||
|
||||
NormalizedURI n = new NormalizedURI();
|
||||
Matcher m = pattern.matcher(resource);
|
||||
|
||||
if (m.matches()) {
|
||||
n.scheme = m.group(1); // includes colon and maybe initial slashes
|
||||
n.user = m.group(2); // includes at sign
|
||||
n.hostportpath = m.group(4);
|
||||
n.query = m.group(5); // includes question mark
|
||||
n.hash = m.group(7); // includes hash mark
|
||||
|
||||
// normalize scheme portion
|
||||
if (Strings.isNullOrEmpty(n.scheme)) {
|
||||
if (!Strings.isNullOrEmpty(n.user)) {
|
||||
// no scheme, but have a user, assume acct:
|
||||
n.scheme = "acct:";
|
||||
} else {
|
||||
// no scheme, no user, assume https://
|
||||
n.scheme = "https://";
|
||||
}
|
||||
}
|
||||
|
||||
n.source = Strings.nullToEmpty(n.scheme) +
|
||||
Strings.nullToEmpty(n.user) +
|
||||
Strings.nullToEmpty(n.hostportpath) +
|
||||
Strings.nullToEmpty(n.query); // note: leave fragment off
|
||||
|
||||
return n;
|
||||
} else {
|
||||
logger.warn("Parser couldn't match input: " + resource);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
URI uri = new URI(resource);
|
||||
|
||||
if (!Strings.isNullOrEmpty(uri.getAuthority())) {
|
||||
|
||||
if (!Strings.isNullOrEmpty(uri.getScheme())) {
|
||||
// there's already a scheme, we'll keep it
|
||||
|
||||
// strip the fragment
|
||||
|
||||
URI noFragment = new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), uri.getQuery(), null);
|
||||
|
||||
return noFragment;
|
||||
|
||||
} else {
|
||||
// no scheme
|
||||
|
||||
if (Strings.isNullOrEmpty(uri.getUserInfo())) {
|
||||
// no "user" portion, treat as https
|
||||
|
||||
URI https = new URI("https", uri.getAuthority(), uri.getPath(), uri.getQuery(), null);
|
||||
|
||||
return https;
|
||||
|
||||
} else {
|
||||
// it's a user@host format, prepend the "acct:" scheme
|
||||
URI acct = new URI("acct", uri.getAuthority(), uri.getPath(), uri.getQuery(), null);
|
||||
|
||||
return acct;
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
// no authority section, this is an error
|
||||
logger.warn("No authority section: " + resource);
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (URISyntaxException e) {
|
||||
logger.warn("Failed parsing input URI: " + resource, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -136,21 +146,71 @@ public class WebfingerIssuerService implements IssuerService {
|
|||
* @author jricher
|
||||
*
|
||||
*/
|
||||
private class WebfingerIssuerFetcher extends CacheLoader<URI, String> {
|
||||
private class WebfingerIssuerFetcher extends CacheLoader<NormalizedURI, String> {
|
||||
private HttpClient httpClient = new DefaultHttpClient();
|
||||
private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
||||
private RestTemplate restTemplate = new RestTemplate(httpFactory);
|
||||
/* (non-Javadoc)
|
||||
* @see com.google.common.cache.CacheLoader#load(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public String load(URI key) throws Exception {
|
||||
// TODO Auto-generated method stub
|
||||
public String load(NormalizedURI key) throws Exception {
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate(httpFactory);
|
||||
// construct the URL to go to
|
||||
|
||||
//String url = "https://" + key.hostportpath + "/.well-known/webfinger?resource="
|
||||
String scheme = key.scheme;
|
||||
if (!Strings.isNullOrEmpty(scheme) && !scheme.startsWith("http")) {
|
||||
// do discovery on http or https URLs
|
||||
scheme = "https://";
|
||||
}
|
||||
URIBuilder builder = new URIBuilder(scheme + key.hostportpath + "/.well-known/webfinger" + Strings.nullToEmpty(key.query));
|
||||
builder.addParameter("resource", key.source);
|
||||
builder.addParameter("rel", "http://openid.net/specs/connect/1.0/issuer");
|
||||
logger.info("Loading: " + builder.toString());
|
||||
String webfingerResponse = restTemplate.getForObject(builder.build(), String.class);
|
||||
|
||||
JsonElement json = new JsonParser().parse(webfingerResponse);
|
||||
if (json != null && json.isJsonObject()) {
|
||||
// find the issuer
|
||||
JsonArray links = json.getAsJsonObject().get("links").getAsJsonArray();
|
||||
for (JsonElement link : links) {
|
||||
if (link.isJsonObject()) {
|
||||
JsonObject linkObj = link.getAsJsonObject();
|
||||
if (linkObj.has("href")
|
||||
&& linkObj.has("rel")
|
||||
&& linkObj.get("rel").getAsString().equals("http://openid.net/specs/connect/1.0/issuer")) {
|
||||
return linkObj.get("href").getAsString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we couldn't find it
|
||||
logger.warn("Couldn't find issuer");
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple data shuttle class to represent the parsed components of a URI.
|
||||
*
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
private class NormalizedURI {
|
||||
|
||||
public String scheme;
|
||||
public String user;
|
||||
public String hostportpath;
|
||||
public String query;
|
||||
public String hash;
|
||||
public String source;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue