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

com.nimbusds.openid.connect.provider.spi.claims.http.HTTPClaimsSource Maven / Gradle / Ivy

The newest version!
package com.nimbusds.openid.connect.provider.spi.claims.http;


import com.nimbusds.common.contenttype.ContentType;
import com.nimbusds.langtag.LangTag;
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
import com.nimbusds.oauth2.sdk.http.HTTPResponse;
import com.nimbusds.oauth2.sdk.id.Subject;
import com.nimbusds.oauth2.sdk.util.MapUtils;
import com.nimbusds.openid.connect.provider.spi.InitContext;
import com.nimbusds.openid.connect.provider.spi.claims.AdvancedClaimsSource;
import com.nimbusds.openid.connect.provider.spi.claims.ClaimUtils;
import com.nimbusds.openid.connect.provider.spi.claims.ClaimsSourceRequestContext;
import com.nimbusds.openid.connect.sdk.claims.UserInfo;
import net.jcip.annotations.ThreadSafe;
import net.minidev.json.JSONObject;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;


/**
 * Connector for retrieving OpenID Connect claims from an HTTP endpoint.
 */
@ThreadSafe
public class HTTPClaimsSource implements AdvancedClaimsSource {


	/**
	 * The configuration file path.
	 */
	public static final String CONFIG_FILE_PATH = "/WEB-INF/httpClaimsSource.properties";
	
	
	/**
	 * The HTTP connector configuration.
	 */
	private Configuration config;


	/**
	 * Logs the overriding system properties.
	 */
	static void logOverridingSystemProperties() {

		Set overriddenPropNames = new HashSet<>();

		for (String key: System.getProperties().stringPropertyNames()) {

			if (key.startsWith(Configuration.DEFAULT_PREFIX))
				overriddenPropNames.add(key);
		}

		Loggers.MAIN_LOG.info("[CSHTTP0010] Overriding system properties: {}", overriddenPropNames);
	}


	/**
	 * Loads the configuration.
	 *
	 * @param initContext The initialisation context. Must not be
	 *                    {@code null}.
	 *
	 * @return The configuration.
	 *
	 * @throws IOException If loading failed.
	 */
	private static Configuration loadConfiguration(final InitContext initContext)
		throws IOException {
		
		var props = new Properties();
		
		InputStream inputStream = initContext.getResourceAsStream(CONFIG_FILE_PATH);

		if (inputStream != null) {
			props.load(inputStream);
		} else {
			Loggers.MAIN_LOG.warn("[CSHTTP0015] HTTP claims source configuration file {} not found, trying system properties", CONFIG_FILE_PATH);
		}
		
		// Override with any system properties
		logOverridingSystemProperties();
		props.putAll(System.getProperties());

		return new Configuration(props);
	}


	@Override
	public void init(final InitContext initContext)
		throws Exception {

		Loggers.MAIN_LOG.info("[CSHTTP0011] Initializing HTTP claims source...");
		config = loadConfiguration(initContext);
		config.log();
	}
	
	
	/**
	 * Returns the HTTP claims source configuration.
	 *
	 * @return The HTTP claims source configuration, {@code null} if not
	 *         configured yet.
	 */
	public Configuration getConfiguration() {
		
		return config;
	}


	@Override
	public boolean isEnabled() {

		return config.enable;
	}


	@Override
	public Set supportedClaims() {

		if (! config.enable) {
			// Empty set
			return Collections.emptySet();
		}

		return config.supportedClaims;
	}
	
	
	@Override
	public UserInfo getClaims(final Subject subject,
				  final Set claims,
				  final List claimsLocales,
				  final ClaimsSourceRequestContext ctx)
		throws Exception {

		if (! config.enable) {
			return null;
		}
		
		// Apply the preferred language tags if any
		Set individualClaims = ClaimUtils.applyLangTags(claims, claimsLocales);
		
		// Construct the request JSON object
		var requestJSONObject = new JSONObject();
		requestJSONObject.put("iss", ctx.getIssuer().getValue());
		requestJSONObject.put("sub", subject.getValue());
		requestJSONObject.put("claims", new LinkedList<>(individualClaims));
		
		if (ctx.getClaimsTransport() != null) {
			requestJSONObject.put("claims_transport", ctx.getClaimsTransport().name().toLowerCase());
		}
		
		if (MapUtils.isNotEmpty(ctx.getClaimsData())) {
			requestJSONObject.put("claims_data", ctx.getClaimsData());
		}

		if (config.includeSubjectSessionID && ctx.getSubjectSessionID() != null) {
			requestJSONObject.put("sub_sid", ctx.getSubjectSessionID().getValue());
		}

		if (config.includeSubjectSession && ctx.getSubjectSession() != null) {
			requestJSONObject.put("sub_session", JSONUtils.toJSONObject(ctx.getSubjectSession()));
		}

		if (config.includeScope && ctx.getScope() != null) {
			requestJSONObject.put("scope", ctx.getScope().toStringList());
		}
		
		JSONObject claimsJSONObject;

		try {
			var httpRequest = new HTTPRequest(HTTPRequest.Method.POST, config.url.toURL());
			httpRequest.setEntityContentType(ContentType.APPLICATION_JSON);
			httpRequest.setAccept(ContentType.APPLICATION_JSON.getType());
			httpRequest.setAuthorization(config.apiAccessToken.toAuthorizationHeader());
			httpRequest.setConnectTimeout(config.connectTimeout);
			httpRequest.setReadTimeout(config.readTimeout);
			httpRequest.setBody(requestJSONObject.toJSONString());
			
			HTTPResponse httpResponse = httpRequest.send();
			httpResponse.ensureStatusCode(200);
			claimsJSONObject = httpResponse.getBodyAsJSONObject();

		} catch (Exception e) {
			Loggers.USERINFO_LOG.error("[CSHTTP0014] UserInfo retrieval error: {} {}", e.getMessage(), e);
			throw new Exception("Couldn't get UserInfo for subject \"" + subject + "\": " + e.getMessage(), e);
		}
		
		if (claimsJSONObject.isEmpty()) {
			// Invalid subject or not found
			return null;
		}
		
		
		if (! claimsJSONObject.containsKey("sub")) {
			// Make sure we have the subject in the JSON object
			// before we create a UserInfo object from it
			claimsJSONObject.put("sub", subject.getValue());
		}

		
		try {
			return new UserInfo(claimsJSONObject);

		} catch (Exception e) {
			Loggers.USERINFO_LOG.error("[CSHTTP0013] UserInfo construction error: {} {}", e.getMessage(), e);
			throw new Exception("Couldn't create UserInfo object: " + e.getMessage(), e);
		}
	}


	@Override
	public void shutdown() {

		Loggers.MAIN_LOG.info("[CSHTTP0012] Shutting down HTTP claims source...");
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy