switched to using UriComponents instead of custom class, updated normalization and processing rules for webfinger discovery

addresses #363
pull/369/merge
Justin Richer 2013-06-27 12:16:08 -04:00
parent 8cf83f537a
commit 77c5e7b94c
1 changed files with 48 additions and 64 deletions

View File

@ -37,6 +37,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
@ -60,7 +62,7 @@ public class WebfingerIssuerService implements IssuerService {
private static final Pattern pattern = Pattern.compile("(https://|acct:|http://|mailto:)?(([^@]+)@)?([^\\?]+)(\\?([^#]+))?(#(.*))?"); private static final Pattern pattern = Pattern.compile("(https://|acct:|http://|mailto:)?(([^@]+)@)?([^\\?]+)(\\?([^#]+))?(#(.*))?");
// map of user input -> issuer, loaded dynamically from webfinger discover // map of user input -> issuer, loaded dynamically from webfinger discover
private LoadingCache<NormalizedURI, String> issuers; private LoadingCache<UriComponents, String> issuers;
private Set<String> whitelist = new HashSet<String>(); private Set<String> whitelist = new HashSet<String>();
private Set<String> blacklist = new HashSet<String>(); private Set<String> blacklist = new HashSet<String>();
@ -114,7 +116,7 @@ public class WebfingerIssuerService implements IssuerService {
* @param identifier * @param identifier
* @return the normalized string, or null if the string can't be normalized * @return the normalized string, or null if the string can't be normalized
*/ */
private NormalizedURI normalizeResource(String identifier) { private UriComponents normalizeResource(String identifier) {
// try to parse the URI // try to parse the URI
// NOTE: we can't use the Java built-in URI class because it doesn't split the parts appropriately // NOTE: we can't use the Java built-in URI class because it doesn't split the parts appropriately
@ -123,38 +125,30 @@ public class WebfingerIssuerService implements IssuerService {
return null; // nothing we can do return null; // nothing we can do
} else { } else {
NormalizedURI n = new NormalizedURI(); UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(identifier);
Matcher m = pattern.matcher(identifier); UriComponents n = builder.build();
if (m.matches()) { if (Strings.isNullOrEmpty(n.getScheme())) {
n.scheme = m.group(1); // includes colon and maybe initial slashes if (!Strings.isNullOrEmpty(n.getUserInfo())
n.user = m.group(2); // includes at sign && Strings.isNullOrEmpty(n.getPath())
n.hostportpath = m.group(4); && Strings.isNullOrEmpty(n.getQuery())
n.query = m.group(5); // includes question mark && n.getPort() < 0) {
n.hash = m.group(7); // includes hash mark
// scheme empty, userinfo is not empty, path/query/port are empty
// set to "acct" (rule 2)
builder.scheme("acct");
// 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 { } else {
// no scheme, no user, assume https:// // scheme is empty, but rule 2 doesn't apply
n.scheme = "https://"; // set scheme to "https" (rule 3)
builder.scheme("https");
} }
} }
n.source = Strings.nullToEmpty(n.scheme) + // fragment must be stripped (rule 4)
Strings.nullToEmpty(n.user) + builder.fragment(null);
Strings.nullToEmpty(n.hostportpath) +
Strings.nullToEmpty(n.query); // note: leave fragment off
return n;
} else {
logger.warn("Parser couldn't match input: " + identifier);
return null;
}
return builder.build();
} }
@ -222,25 +216,26 @@ public class WebfingerIssuerService implements IssuerService {
* @author jricher * @author jricher
* *
*/ */
private class WebfingerIssuerFetcher extends CacheLoader<NormalizedURI, String> { private class WebfingerIssuerFetcher extends CacheLoader<UriComponents, String> {
private HttpClient httpClient = new DefaultHttpClient(); private HttpClient httpClient = new DefaultHttpClient();
private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
private JsonParser parser = new JsonParser(); private JsonParser parser = new JsonParser();
@Override @Override
public String load(NormalizedURI key) throws Exception { public String load(UriComponents key) throws Exception {
RestTemplate restTemplate = new RestTemplate(httpFactory); RestTemplate restTemplate = new RestTemplate(httpFactory);
// construct the URL to go to // construct the URL to go to
//String url = "https://" + key.hostportpath + "/.well-known/webfinger?resource=" // do a webfinger lookup
String scheme = key.scheme; URIBuilder builder = new URIBuilder("https://"
if (!Strings.isNullOrEmpty(scheme) && !scheme.startsWith("http")) { + key.getHost()
// do discovery on http or https URLs + (key.getPort() >= 0 ? ":" + key.getPort() : "")
scheme = "https://"; + Strings.nullToEmpty(key.getPath())
} + "/.well-known/webfinger"
URIBuilder builder = new URIBuilder(scheme + key.hostportpath + "/.well-known/webfinger" + Strings.nullToEmpty(key.query)); + (Strings.isNullOrEmpty(key.getQuery()) ? "" : "?" + key.getQuery())
builder.addParameter("resource", key.source); );
builder.addParameter("resource", key.toString());
builder.addParameter("rel", "http://openid.net/specs/connect/1.0/issuer"); builder.addParameter("rel", "http://openid.net/specs/connect/1.0/issuer");
// do the fetch // do the fetch
@ -271,30 +266,19 @@ public class WebfingerIssuerService implements IssuerService {
} }
// we couldn't find it // we couldn't find it
logger.warn("Couldn't find issuer");
if (key.getScheme().equals("http") || key.getScheme().equals("https")) {
// if it looks like HTTP then punt and return the input
logger.warn("Returning normalized input string as issuer, hoping for the best: " + key.toString());
return key.toString();
} else {
// if it's not HTTP, give up
logger.warn("Couldn't find issuer: " + key.toString());
return null; 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;
} }
} }