All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openid4java.discovery.yadis.YadisResolver Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2006-2008 Sxip Identity Corporation
 */

package org.openid4java.discovery.yadis;

import com.google.inject.Inject;

import org.apache.http.HttpException;
import org.apache.http.HttpStatus;
import org.apache.http.Header;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.ClientProtocolException;

import java.io.IOException;
import java.util.Set;
import java.util.Collections;
import java.util.List;

import org.openid4java.OpenIDException;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.discovery.DiscoveryException;
import org.openid4java.discovery.xrds.XrdsParser;
import org.openid4java.util.HttpCache;
import org.openid4java.util.HttpFetcher;
import org.openid4java.util.HttpFetcherFactory;
import org.openid4java.util.HttpRequestOptions;
import org.openid4java.util.HttpResponse;
import org.openid4java.util.OpenID4JavaUtils;



/**
 * Yadis discovery protocol implementation.
 * 

* Yadis discovery protocol returns a Yadis Resource Descriptor (XRDS) document * associated with a Yadis Identifier (YadisID) *

* YadisIDs can be any type of identifiers that are resolvable to a URL form, * and in addition the URL form uses a HTTP or a HTTPS schema. Such an URL * is defined by the Yadis speficification as a YadisURL. This functionality * is implemented by the YadisURL helper class. *

* The discovery of the XRDS document is performed by the discover method * on a YadisUrl. *

* Internal parameters used during the discovery process : *

    *
  • max redirects (default 10): maximum number of redirects to be followed * for YadisURL *
* * @author Marius Scurtescu, Johnny Bufu, Sutra Zhou */ public class YadisResolver { private static Log _log = LogFactory.getLog(YadisResolver.class); private static final boolean DEBUG = _log.isDebugEnabled(); // Yadis constants public static final String YADIS_XRDS_LOCATION = "X-XRDS-Location"; private static final String YADIS_CONTENT_TYPE = "application/xrds+xml"; private static final String YADIS_ACCEPT_HEADER = "text/html; q=0.3, application/xhtml+xml; q=0.5, " + YADIS_CONTENT_TYPE; private static final String YADIS_HTML_PARSER_CLASS_NAME_KEY = "discovery.yadis.html.parser"; private static final YadisHtmlParser YADIS_HTML_PARSER; private static final String XRDS_PARSER_CLASS_NAME_KEY = "discovery.xrds.parser"; private static final XrdsParser XRDS_PARSER; static { String className = OpenID4JavaUtils.getProperty(YADIS_HTML_PARSER_CLASS_NAME_KEY); if (DEBUG) _log.debug(YADIS_HTML_PARSER_CLASS_NAME_KEY + ":" + className); try { YADIS_HTML_PARSER = (YadisHtmlParser) Class.forName(className).newInstance(); } catch (Exception e) { throw new RuntimeException(e); } className = OpenID4JavaUtils.getProperty(XRDS_PARSER_CLASS_NAME_KEY); if (DEBUG) _log.debug(XRDS_PARSER_CLASS_NAME_KEY + ":" + className); try { XRDS_PARSER = (XrdsParser) Class.forName(className).newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } /** * Maximum number of redirects to be followed for the HTTP calls. * Defalut 10. */ private int _maxRedirects = 10; private final HttpFetcher _httpFetcher; /** * Gets the internal limit configured for the maximum number of redirects * to be followed for the HTTP calls. */ public int getMaxRedirects() { return _maxRedirects; } /** * Sets the maximum number of redirects to be followed for the HTTP calls. */ public void setMaxRedirects(int maxRedirects) { this._maxRedirects = maxRedirects; } @Inject public YadisResolver(HttpFetcherFactory httpFetcherFactory) { this(httpFetcherFactory.createFetcher( HttpRequestOptions.getDefaultOptionsForDiscovery())); } public YadisResolver(HttpFetcher httpFetcher) { _httpFetcher = httpFetcher; } /** * Performs Relyin Party discovery on the supplied URL. * * @param url RP's realm or return_to URL * @return List of DiscoveryInformation entries discovered * from the RP's endpoints */ public List discoverRP(String url) throws DiscoveryException { return discover(url, 0, Collections.singleton(DiscoveryInformation.OPENID2_RP)) .getDiscoveredInformation(Collections.singleton(DiscoveryInformation.OPENID2_RP)); } /** * Performs Yadis discovery on the YadisURL. *

*

    *
  • tries to retrieve the XRDS location via a HEAD call on the Yadis URL *
  • retrieves the XRDS document with a GET on the above if available, * or through a GET on the YadisURL otherwise *
*

* The maximum number of redirects that are followed is determined by the * #_maxRedirects member field. * * @param url YadisURL on which discovery will be performed * @return List of DiscoveryInformation entries discovered * obtained from the URL Identifier. * @see YadisResult #discover(String, int, HttpCache) */ public List discover(String url) throws DiscoveryException { return discover(url, _maxRedirects, _httpFetcher); } /** * Performs Yadis discovery on the YadisURL. *

*

    *
  • tries to retrieve the XRDS location via a HEAD call on the Yadis URL *
  • retrieves the XRDS document with a GET on the above if available, * or through a GET on the YadisURL otherwise *
*

* The maximum number of redirects that are followed is determined by the * #_maxRedirects member field. * * @param url YadisURL on which discovery will be performed * @param httpFetcher {@link HttpFetcher} object to use for the call * @return List of DiscoveryInformation entries discovered * obtained from the URL Identifier. * @see YadisResult #discover(String, int, HttpCache) */ public List discover(String url, HttpFetcher httpFetcher) throws DiscoveryException { return discover(url, _maxRedirects, httpFetcher); } /** * Performs Yadis discovery on the YadisURL. *

*

    *
  • tries to retrieve the XRDS location via a HEAD call on the Yadis URL *
  • retrieves the XRDS document with a GET on the above if available, * or through a GET on the YadisURL otherwise *
* * @param url YadisURL on which discovery will be performed * @param maxRedirects The maximum number of redirects to be followed. * @return List of DiscoveryInformation entries discovered * obtained from the URL Identifier. * @see YadisResult */ public List discover(String url, int maxRedirects) throws DiscoveryException { return discover(url, maxRedirects, _httpFetcher); } /** * Performs Yadis discovery on the YadisURL. *

*

    *
  • tries to retrieve the XRDS location via a HEAD call on the Yadis URL *
  • retrieves the XRDS document with a GET on the above if available, * or through a GET on the YadisURL otherwise *
* * @param url YadisURL on which discovery will be performed * @param maxRedirects The maximum number of redirects to be followed. * @param httpFetcher {@link HttpFetcher} object to use for the call. * @return List of DiscoveryInformation entries discovered * obtained from the URL Identifier. * @see YadisResult */ public List discover(String url, int maxRedirects, HttpFetcher httpFetcher) throws DiscoveryException { return discover(url, maxRedirects, httpFetcher, DiscoveryInformation.OPENID_OP_TYPES) .getDiscoveredInformation(DiscoveryInformation.OPENID_OP_TYPES); } public YadisResult discover(String url, int maxRedirects, Set serviceTypes) throws DiscoveryException { return discover(url, maxRedirects, _httpFetcher, serviceTypes); } public YadisResult discover(String url, int maxRedirects, HttpFetcher httpFetcher, Set serviceTypes) throws DiscoveryException { YadisUrl yadisUrl = new YadisUrl(url); // try to retrieve the Yadis Descriptor URL with a HEAD call first YadisResult result = retrieveXrdsLocation(yadisUrl, false, maxRedirects, serviceTypes); // try GET if (result.getXrdsLocation() == null) result = retrieveXrdsLocation(yadisUrl, true, maxRedirects, serviceTypes); if (result.getXrdsLocation() != null) { retrieveXrdsDocument(result, maxRedirects, serviceTypes); } else if (result.hasEndpoints()) { // report the yadis url as the xrds location result.setXrdsLocation(url, OpenIDException.YADIS_INVALID_URL); } _log.info("Yadis discovered " + result.getEndpointCount() + " endpoints from: " + url); return result; } /** * Tries to retrieve the XRDS document via a GET call on XRDS location * provided in the result parameter. * * @param result The YadisResult object containing a valid XRDS location. * It will be further populated with the Yadis discovery results. * @param cache The HttpClient object to use for placing the call * @param maxRedirects */ private void retrieveXrdsDocument(YadisResult result, int maxRedirects, Set serviceTypes) throws DiscoveryException { _httpFetcher.getRequestOptions().setMaxRedirects(maxRedirects); try { HttpResponse resp = _httpFetcher.get(result.getXrdsLocation().toString()); if (resp == null || HttpStatus.SC_OK != resp.getStatusCode()) throw new YadisException("GET failed on " + result.getXrdsLocation(), OpenIDException.YADIS_GET_ERROR); // update xrds location, in case redirects were followed result.setXrdsLocation(resp.getFinalUri(), OpenIDException.YADIS_GET_INVALID_RESPONSE); Header contentType = resp.getResponseHeader("content-type"); if ( contentType != null && contentType.getValue() != null) result.setContentType(contentType.getValue()); if (resp.isBodySizeExceeded()) throw new YadisException( "More than " + _httpFetcher.getRequestOptions().getMaxBodySize() + " bytes in HTTP response body from " + result.getXrdsLocation(), OpenIDException.YADIS_XRDS_SIZE_EXCEEDED); result.setEndpoints(XRDS_PARSER.parseXrds(resp.getBody(), serviceTypes)); } catch (IOException e) { throw new YadisException("Fatal transport error: " + e.getMessage(), OpenIDException.YADIS_GET_TRANSPORT_ERROR, e); } } /** * Parses the HTML input stream and scans for the Yadis XRDS location * in the HTML HEAD Meta tags. * * @param input input data stream * @return String the XRDS location URL, or null if not found * @throws YadisException on parsing errors or Yadis protocal violations */ private String getHtmlMeta(String input) throws YadisException { String xrdsLocation; if (input == null) throw new YadisException("Cannot download HTML message", OpenIDException.YADIS_HTMLMETA_DOWNLOAD_ERROR); xrdsLocation = YADIS_HTML_PARSER.getHtmlMeta(input); if (DEBUG) { _log.debug("input:\n" + input); _log.debug("xrdsLocation: " + xrdsLocation); } return xrdsLocation; } /** * Tries to retrieve the XRDS location url by performing a cheap HEAD call * on the YadisURL. *

* The returned string should be validated before being used * as a XRDS-Location URL. * * @param cache HttpClient object to use for placing the call * @param maxRedirects * @param url The YadisURL * @param result The location of the XRDS document and the normalized * Url will be returned in the YadisResult object. *

* The location of the XRDS document will be null if: *

    *
  • the returned status code is different than SC_OK *
  • the Yadis header is not present *
  • there was an HTTP-level error * (allows fallback to GET + HTML response) *
* @throws YadisException if: *
    *
  • there's a (lower level) transport error *
  • there are more than one Yadis headers present *
*/ private YadisResult retrieveXrdsLocation( YadisUrl url, boolean useGet, int maxRedirects, Set serviceTypes) throws DiscoveryException { int maxattempts = 1; /*** * Need to try GET twice in some cases, because some major RPs do a redirect * when Accept header is set to YADIS_ACCEPT_HEADER * So, we need to retry with Accept header YADIS_CONTENT_TYPE */ if (useGet) maxattempts = 2; YadisResult result = new YadisResult(); for (int attempt = 1; attempt <= maxattempts; attempt++) { try { result.setYadisUrl(url); if (DEBUG) _log.debug( "Performing HTTP " + (useGet ? "GET" : "HEAD") + " on: " + url + " ..."); HttpRequestOptions requestOptions = _httpFetcher.getRequestOptions(); requestOptions.setMaxRedirects(maxRedirects); if (useGet) { if (attempt == 1) requestOptions.addRequestHeader("Accept", YADIS_ACCEPT_HEADER); else requestOptions.addRequestHeader("Accept", YADIS_CONTENT_TYPE); } HttpResponse resp = useGet ? _httpFetcher.get(url.getUrl().toString(), requestOptions) : _httpFetcher.head(url.getUrl().toString(), requestOptions); Header[] locationHeaders = resp.getResponseHeaders(YADIS_XRDS_LOCATION); Header contentType = resp.getResponseHeader("content-type"); if (HttpStatus.SC_OK != resp.getStatusCode()) { // won't be able to recover from a GET error, throw if (useGet) throw new YadisException("GET failed on " + url + " : " + resp.getStatusCode(), OpenIDException.YADIS_GET_ERROR); // HEAD is optional, will fall-back to GET if (DEBUG) _log.debug("Cannot retrieve " + YADIS_XRDS_LOCATION + " using HEAD from " + url.getUrl().toString() + "; status=" + resp.getStatusCode()); } else if ((locationHeaders != null && locationHeaders.length > 1)) { // fail if there are more than one YADIS_XRDS_LOCATION headers throw new YadisException("Found " + locationHeaders.length + " " + YADIS_XRDS_LOCATION + " headers.", useGet ? OpenIDException.YADIS_GET_INVALID_RESPONSE : OpenIDException.YADIS_HEAD_INVALID_RESPONSE); } else if (locationHeaders != null && locationHeaders.length > 0) { // we have exactly one xrds location header result.setXrdsLocation(locationHeaders[0].getValue(), useGet ? OpenIDException.YADIS_GET_INVALID_RESPONSE : OpenIDException.YADIS_HEAD_INVALID_RESPONSE); result.setNormalizedUrl(resp.getFinalUri()); } else if (contentType != null && contentType.getValue() != null && contentType.getValue().split(";")[0].equalsIgnoreCase(YADIS_CONTENT_TYPE) && resp.getBody() != null) { // no location, but got xrds document result.setNormalizedUrl(resp.getFinalUri()); result.setContentType(contentType.getValue()); if (resp.isBodySizeExceeded()) throw new YadisException( "More than " + requestOptions.getMaxBodySize() + " bytes in HTTP response body from " + url, OpenIDException.YADIS_XRDS_SIZE_EXCEEDED); result.setEndpoints(XRDS_PARSER.parseXrds(resp.getBody(), serviceTypes)); } else if (resp.getBody() != null) { // fall-back to html-meta, if present String xrdsLocation = getHtmlMeta(resp.getBody()); if (xrdsLocation != null) { result.setNormalizedUrl(resp.getFinalUri()); result.setXrdsLocation(xrdsLocation, OpenIDException.YADIS_GET_INVALID_RESPONSE); } } return result; } catch (ClientProtocolException e) { if (useGet && attempt == 2) throw new YadisException("ClientProtocol error: " + e.getMessage(), OpenIDException.YADIS_HEAD_TRANSPORT_ERROR, e); else if (useGet && attempt == 1) continue; return result; } catch (IOException e) { throw new YadisException("I/O transport error: " + e.getMessage(), OpenIDException.YADIS_HEAD_TRANSPORT_ERROR, e); } } return result; } /* visible for testing */ public HttpFetcher getHttpFetcher() { return _httpFetcher; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy