[java-identity-provider] branch master updated: IDP-1170 - REST DataConnector
Scott Cantor
cantor.2 at osu.edu
Sat May 20 19:00:17 EDT 2017
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch master
in repository java-identity-provider.
View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=39617ee9b094ba61f599fd3d416e2e67e9689a87
The following commit(s) were added to refs/heads/master by this push:
new 39617ee IDP-1170 - REST DataConnector
39617ee is described below
commit 39617ee9b094ba61f599fd3d416e2e67e9689a87
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Sat May 20 19:00:15 2017 -0400
IDP-1170 - REST DataConnector
https://issues.shibboleth.net/jira/browse/IDP-1170
Connector and plugin implementations.
---
.../dc/http/impl/AbstractHTTPSearchBuilder.java | 144 +++++++++
.../resolver/dc/http/impl/HTTPDataConnector.java | 93 ++++++
.../dc/http/impl/HTTPResponseMappingStrategy.java | 34 ++
.../resolver/dc/http/impl/HTTPSearch.java | 45 +++
.../http/impl/ScriptedResponseMappingStrategy.java | 344 +++++++++++++++++++++
.../resolver/dc/http/impl/TemplatedURLBuilder.java | 194 ++++++++++++
.../resolver/dc/http/impl/package-info.java | 22 ++
.../dc/http/impl/HTTPDataConnectorTest.java | 187 +++++++++++
.../idp/attribute/resolver/impl/dc/http/test.js | 20 ++
9 files changed, 1083 insertions(+)
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/AbstractHTTPSearchBuilder.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/AbstractHTTPSearchBuilder.java
new file mode 100644
index 0000000..d3288e1
--- /dev/null
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/AbstractHTTPSearchBuilder.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.dc.http.impl;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.http.HttpRequest;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.opensaml.security.httpclient.HttpClientSecurityParameters;
+import org.opensaml.security.httpclient.HttpClientSecuritySupport;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.dc.impl.ExecutableSearchBuilder;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Basis of request builder. Derived classes just have to provide the per request URI but may override
+ * the complete request build if desired, for example to construct a SOAP message or something more
+ * exotic.
+ *
+ * <p>This is all a bit byzantine to maintain a consistent design with the LDAP/etc. connectors,
+ * which split the work of producing "search objects that execute and return a result" and
+ * "mapping strategies that process a result". The HTTP client supports response handlers
+ * that offload all the connection cleanup and avoid any extra data buffering, so our
+ * facade passes the mapping strategy in as a response handler and just returns the result.</p>
+ */
+public abstract class AbstractHTTPSearchBuilder extends AbstractInitializableComponent implements
+ ExecutableSearchBuilder<HTTPSearch> {
+
+ /** HTTP client security parameters. */
+ @Nullable private HttpClientSecurityParameters httpClientSecurityParameters;
+
+ /**
+ * Set the optional client security parameters.
+ *
+ * @param params the new client security parameters
+ */
+ public void setHttpClientSecurityParameters(@Nullable final HttpClientSecurityParameters params) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ httpClientSecurityParameters = params;
+ }
+
+ /** {@inheritDoc} */
+ @Override public HTTPSearch build(@Nonnull final AttributeResolutionContext resolutionContext,
+ @Nonnull final Map<String, List<IdPAttributeValue<?>>> dependencyAttributes) throws ResolutionException {
+
+ final HttpUriRequest request = getHttpRequest(resolutionContext, dependencyAttributes);
+ final HttpClientContext context = buildHttpContext(request);
+
+ return new HTTPSearch() {
+ /** {@inheritDoc} */
+ @Nonnull public String getResultCacheKey() {
+ Constraint.isTrue(request instanceof HttpGet, "Only GET requests are cacheable");
+ return ((HttpGet) request).getURI().toString();
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return request.getRequestLine().getUri();
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull public Map<String,IdPAttribute> execute(@Nonnull final HttpClient client,
+ @Nonnull final HTTPResponseMappingStrategy mappingStrategy) throws IOException {
+ final Map<String,IdPAttribute> results = client.execute(request, mappingStrategy, context);
+ HttpClientSecuritySupport.checkTLSCredentialEvaluated(context, request.getURI().getScheme());
+ return results;
+ }
+ };
+ }
+
+ /**
+ * Method to return the URL to access via GET.
+ *
+ * @param resolutionContext the context of the resolution
+ * @param dependencyAttributes made available to the request
+ *
+ * @return the URL to GET
+ * @throws ResolutionException if an error occurs
+ */
+ @Nonnull @NotEmpty protected abstract String getURL(@Nonnull final AttributeResolutionContext resolutionContext,
+ @Nonnull final Map<String,List<IdPAttributeValue<?>>> dependencyAttributes) throws ResolutionException;
+
+ /**
+ * Default implementation just supports GET and builds a request around a URL.
+ *
+ * @param resolutionContext the context of the resolution
+ * @param dependencyAttributes made available to the request
+ *
+ * @return the {@link HttpRequest} to use
+ * @throws ResolutionException if an error occurs
+ */
+ @Nonnull protected HttpUriRequest getHttpRequest(@Nonnull final AttributeResolutionContext resolutionContext,
+ @Nonnull final Map<String,List<IdPAttributeValue<?>>> dependencyAttributes) throws ResolutionException {
+
+ // Default just wraps a computed URL into a GET.
+ return new HttpGet(getURL(resolutionContext, dependencyAttributes));
+ }
+
+ /**
+ * Build the {@link HttpClientContext} instance to be used.
+ *
+ * @param request the HTTP client request
+ * @return the client context instance
+ */
+ @Nonnull private HttpClientContext buildHttpContext(@Nonnull final HttpUriRequest request) {
+ final HttpClientContext clientContext = HttpClientContext.create();
+ HttpClientSecuritySupport.marshalSecurityParameters(clientContext, httpClientSecurityParameters, false);
+ HttpClientSecuritySupport.addDefaultTLSTrustEngineCriteria(clientContext, request);
+ return clientContext;
+ }
+
+}
\ No newline at end of file
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/HTTPDataConnector.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/HTTPDataConnector.java
new file mode 100644
index 0000000..2eb808f
--- /dev/null
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/HTTPDataConnector.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Derived from work (c) 2015 CSC, see included license.
+ */
+
+package net.shibboleth.idp.attribute.resolver.dc.http.impl;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.http.client.HttpClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.dc.ValidationException;
+import net.shibboleth.idp.attribute.resolver.dc.Validator;
+import net.shibboleth.idp.attribute.resolver.dc.impl.AbstractSearchDataConnector;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/** This class implements a {@link DataConnector} that obtains data from an HTTP service. */
+public class HTTPDataConnector extends AbstractSearchDataConnector<HTTPSearch,HTTPResponseMappingStrategy> {
+
+ /** Class logging. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(HTTPDataConnector.class);
+
+ /** The {@link HttpClient} to use. */
+ @NonnullAfterInit private HttpClient httpClient;
+
+ /** Constructor. */
+ public HTTPDataConnector() {
+ setValidator(new Validator() {
+ public void validate() throws ValidationException {
+ }
+ });
+ }
+
+ /**
+ * Set the {@link HttpClient} to use.
+ *
+ * @param client client to use
+ */
+ public void setHttpClient(@Nonnull final HttpClient client) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ httpClient = Constraint.isNotNull(client, "HttpClient cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ public void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (httpClient == null) {
+ throw new ComponentInitializationException(getLogPrefix() + " HttpClient cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Map<String,IdPAttribute> retrieveAttributes(@Nonnull final HTTPSearch executable)
+ throws ResolutionException {
+
+ try {
+ return getMappingStrategy().map(executable.execute(httpClient, getMappingStrategy()));
+ } catch (final IOException e) {
+ throw new ResolutionException(getLogPrefix() + " HTTP request failed", e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/HTTPResponseMappingStrategy.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/HTTPResponseMappingStrategy.java
new file mode 100644
index 0000000..1a27f87
--- /dev/null
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/HTTPResponseMappingStrategy.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.dc.http.impl;
+
+import java.util.Map;
+
+import org.apache.http.client.ResponseHandler;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.resolver.dc.MappingStrategy;
+
+/**
+ * Strategy for processing an {@link org.apache.http.client.protocol.HttpClient} response into a map of
+ * {@link net.shibboleth.idp.attribute.IdPAttribute}s.
+ */
+public interface HTTPResponseMappingStrategy extends MappingStrategy<Map<String,IdPAttribute>>,
+ ResponseHandler<Map<String,IdPAttribute>> {
+
+}
\ No newline at end of file
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/HTTPSearch.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/HTTPSearch.java
new file mode 100644
index 0000000..8bf0ec3
--- /dev/null
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/HTTPSearch.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.dc.http.impl;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.apache.http.client.HttpClient;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.resolver.dc.impl.ExecutableSearch;
+
+/** An HTTP request that returns attribute data. */
+public interface HTTPSearch extends ExecutableSearch {
+
+ /**
+ * The abstraction that will contact the service and obtain results.
+ *
+ * @param client the HTTP client
+ * @param mappingStrategy response mapping strategy
+ *
+ * @return attribute results
+ * @throws IOException if an error occurs
+ */
+ @Nonnull Map<String,IdPAttribute> execute(@Nonnull final HttpClient client,
+ @Nonnull final HTTPResponseMappingStrategy mappingStrategy) throws IOException;
+
+}
\ No newline at end of file
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/ScriptedResponseMappingStrategy.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/ScriptedResponseMappingStrategy.java
new file mode 100644
index 0000000..7dae176
--- /dev/null
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/ScriptedResponseMappingStrategy.java
@@ -0,0 +1,344 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.dc.http.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.script.ScriptContext;
+import javax.script.ScriptException;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.entity.ContentType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.utilities.java.support.resource.Resource;
+import net.shibboleth.utilities.java.support.scripting.AbstractScriptEvaluator;
+import net.shibboleth.utilities.java.support.scripting.EvaluableScript;
+
+/**
+ * {@link HTTPResponseMappingStrategy} that relies on a script to map the response to the
+ * attribute set.
+ *
+ * <p>Well-suited to JSON output formats that can be parsed by the scripting engine.</p>
+ */
+public final class ScriptedResponseMappingStrategy extends AbstractScriptEvaluator
+ implements HTTPResponseMappingStrategy {
+
+ /** The id of the object where the results go. */
+ @Nonnull public static final String RESULTS_STRING = "connectorResults";
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ScriptedResponseMappingStrategy.class);
+
+ /** "Successful" statuses. */
+ @Nonnull private Set<Integer> acceptStatuses;
+
+ /** Acceptable Content-Types. */
+ @Nonnull private Set<String> acceptTypes;
+
+ /** Limit on content-length. */
+ private long maxLength;
+
+ /**
+ * Constructor.
+ *
+ * @param theScript the script to run
+ */
+ private ScriptedResponseMappingStrategy(@Nonnull final EvaluableScript theScript) {
+ super(theScript);
+
+ acceptStatuses = Collections.singleton(HttpStatus.SC_OK);
+ acceptTypes = Collections.emptySet();
+ }
+
+ /**
+ * Set the HTTP status codes to treat as successful.
+ *
+ * @param statuses successful codes
+ */
+ public void setAcceptStatuses(@Nonnull final Collection<Integer> statuses) {
+ Constraint.isNotNull(statuses, "Statuses cannot be null");
+
+ acceptStatuses = new HashSet<>(Collections2.filter(statuses, Predicates.notNull()));
+ }
+
+ /**
+ * Set the content-types to allow.
+ *
+ * @param types types to allow
+ */
+ public void setAcceptTypes(@Nonnull final Collection<String> types) {
+ Constraint.isNotNull(types, "Types cannot be null");
+
+ acceptTypes = new HashSet<>(StringSupport.normalizeStringCollection(types));
+ }
+
+ /**
+ * Set a limit on content-length.
+ *
+ * <p>Defaults to 0, allowing any. Setting a limit implies that an unknown length will be rejected.</p>
+ *
+ * @param len limit on size
+ */
+ public void setMaxLength(final long len) {
+ maxLength = len;
+ }
+
+ /** {@inheritDoc} */
+ @Nonnull public Map<String,IdPAttribute> map(@Nonnull final Map<String,IdPAttribute> results)
+ throws ResolutionException {
+ return results;
+ }
+
+ /** {@inheritDoc} */
+ public Map<String,IdPAttribute> handleResponse(final HttpResponse response) throws IOException {
+
+ log.debug("{} Handling HTTP response", getLogPrefix());
+
+ if (response == null) {
+ log.debug("{} HTTP response was missing", getLogPrefix());
+ throw new IOException(getLogPrefix() + " HTTP response was missing");
+ }
+
+ checkStatus(response);
+
+ final HttpEntity entity = response.getEntity();
+ if (entity == null) {
+ log.debug("{} Response body was missing", getLogPrefix());
+ throw new IOException(getLogPrefix() + " Response body was missing");
+ }
+
+ checkContentType(entity);
+ checkContentLength(entity);
+
+ try {
+ return (Map<String,IdPAttribute>) evaluate(response);
+ } catch (final RuntimeException e) {
+ throw new IOException(getLogPrefix() + " Script did not run successfully", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void prepareContext(@Nonnull final ScriptContext scriptContext, @Nullable final Object... input) {
+ log.debug("{} Adding to-be-populated attribute set '{}' to script context", getLogPrefix(), RESULTS_STRING);
+ scriptContext.setAttribute(RESULTS_STRING, new HashSet<>(), ScriptContext.ENGINE_SCOPE);
+
+ scriptContext.setAttribute("response", input[0], ScriptContext.ENGINE_SCOPE);
+ scriptContext.setAttribute("log", log, ScriptContext.ENGINE_SCOPE);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable protected Object finalizeContext(@Nonnull final ScriptContext scriptContext,
+ @Nullable final Object scriptResult) throws ScriptException {
+
+ // The real result in our case is a variable in the context.
+ final Object res = scriptContext.getAttribute(RESULTS_STRING);
+
+ if (null == res) {
+ log.error("{} Could not locate output variable '{}' from script", getLogPrefix(), RESULTS_STRING);
+ throw new ScriptException("Could not locate output from script");
+ }
+ if (!(res instanceof Collection)) {
+ log.error("{} Output '{}' was of type '{}', expected '{}'", getLogPrefix(), res.getClass().getName(),
+ Collection.class.getName());
+ throw new ScriptException("Output was of the wrong type");
+ }
+
+ final Collection outputCollection = (Collection) res;
+ final Map<String, IdPAttribute> outputMap = new HashMap<>(outputCollection.size());
+ for (final Object o : outputCollection) {
+ if (o instanceof IdPAttribute) {
+ final IdPAttribute attribute = (IdPAttribute) o;
+ if (null == attribute.getId()) {
+ log.warn("{} Anonymous Attribute encountered, ignored", getLogPrefix());
+ } else {
+ checkValues(attribute);
+ outputMap.put(attribute.getId(), attribute);
+ }
+ } else {
+ log.warn("{} Output collection contained an object of type '{}', ignored", getLogPrefix(),
+ o.getClass().getName());
+ }
+ }
+
+ return outputMap;
+ }
+
+ /**
+ * Enforce any status code requirements.
+ *
+ * @param response HTTP response
+ *
+ * @throws IOException if the status is unacceptable
+ */
+ private void checkStatus(@Nonnull final HttpResponse response) throws IOException {
+ if (!acceptStatuses.isEmpty()) {
+ if (response.getStatusLine() == null
+ || !acceptStatuses.contains(response.getStatusLine().getStatusCode())) {
+ log.debug("{} Unacceptable HTTP status: {}", getLogPrefix(),
+ response.getStatusLine() != null ? response.getStatusLine().getStatusCode() : "unknown");
+ throw new IOException(getLogPrefix() + " HTTP status unknown or unacceptable");
+ }
+ }
+ }
+
+ /**
+ * Enforce Content-Type requirements.
+ *
+ * @param entity the entity body
+ *
+ * @throws IOException if the type is unacceptable
+ */
+ private void checkContentType(@Nonnull final HttpEntity entity) throws IOException {
+ if (!acceptTypes.isEmpty()) {
+
+ final ContentType contentType = ContentType.get(entity);
+ if (contentType == null || !acceptTypes.contains(contentType.getMimeType())) {
+ log.debug("{} Unacceptable Content-Type: {}", getLogPrefix(),
+ contentType != null ? contentType.getMimeType() : "unknown");
+ throw new IOException(getLogPrefix() + " Content-Type unknown or unacceptable");
+ }
+ }
+ }
+
+ /**
+ * Check the content length.
+ *
+ * @param entity the entity body
+ *
+ * @throws IOException if the length is unacceptable
+ */
+ private void checkContentLength(@Nonnull final HttpEntity entity) throws IOException {
+ if (maxLength > 0) {
+ if (entity.getContentLength() < 0 || entity.getContentLength() > maxLength) {
+ log.debug("{} Unacceptable Content-Length: {}", getLogPrefix(), entity.getContentLength());
+ throw new IOException(getLogPrefix() + " Content-Length exceeded acceptable limits or was unset");
+ }
+ }
+ }
+
+ /**
+ * Ensure that all the values in the attribute are of the correct type.
+ *
+ * @param attribute the attribute to look at
+ */
+ private void checkValues(final IdPAttribute attribute) {
+
+ if (null == attribute.getValues()) {
+ log.info("{} Attribute '{}' has no values provided.", getLogPrefix(), attribute.getId());
+ attribute.setValues(Collections.<IdPAttributeValue<?>> emptyList());
+ return;
+ }
+ log.debug("{} Attribute '{}' has {} value(s).", getLogPrefix(), attribute.getId(),
+ attribute.getValues().size());
+ final List<IdPAttributeValue<?>> inputValues = attribute.getValues();
+ final List<IdPAttributeValue<?>> outputValues = new ArrayList<>(inputValues.size());
+
+ for (final Object o : inputValues) {
+ if (o instanceof IdPAttributeValue<?>) {
+ outputValues.add((IdPAttributeValue<?>) o);
+ } else {
+ log.error("{} Attribute '{} has attribute value of type {}. This will be ignored", getLogPrefix(),
+ attribute.getId(), o.getClass().getName());
+ }
+ }
+ attribute.setValues(outputValues);
+ }
+
+ /**
+ * Factory to create {@link ScriptedResponseMappingStrategy} from a {@link Resource}.
+ *
+ * @param engineName the language
+ * @param resource the resource to look at
+ * @return the function
+ * @throws ScriptException if the compile fails
+ * @throws IOException if the file doesn't exist.
+ */
+ @Nonnull static ScriptedResponseMappingStrategy resourceScript(@Nonnull @NotEmpty final String engineName,
+ @Nonnull final Resource resource) throws ScriptException, IOException {
+ try (final InputStream is = resource.getInputStream()) {
+ final EvaluableScript script = new EvaluableScript(engineName, is);
+ return new ScriptedResponseMappingStrategy(script);
+ }
+ }
+
+ /**
+ * Factory to create {@link ScriptedResponseMappingStrategy} from a {@link Resource}.
+ *
+ * @param resource the resource to look at
+ * @return the function
+ * @throws ScriptException if the compile fails
+ * @throws IOException if the file doesn't exist.
+ */
+ @Nonnull static ScriptedResponseMappingStrategy resourceScript(@Nonnull final Resource resource)
+ throws ScriptException, IOException {
+ return resourceScript(DEFAULT_ENGINE, resource);
+ }
+
+ /**
+ * Factory to create {@link ScriptedResponseMappingStrategy} from inline data.
+ *
+ * @param scriptSource the script, as a string
+ * @param engineName the language
+ * @return the function
+ * @throws ScriptException if the compile fails
+ */
+ @Nonnull static ScriptedResponseMappingStrategy inlineScript(@Nonnull @NotEmpty final String engineName,
+ @Nonnull @NotEmpty final String scriptSource) throws ScriptException {
+ final EvaluableScript script = new EvaluableScript(engineName, scriptSource);
+ return new ScriptedResponseMappingStrategy(script);
+ }
+
+ /**
+ * Factory to create {@link ScriptedResponseMappingStrategy} from inline data.
+ *
+ * @param scriptSource the script, as a string
+ * @return the function
+ * @throws ScriptException if the compile fails
+ */
+ @Nonnull static ScriptedResponseMappingStrategy inlineScript(@Nonnull @NotEmpty final String scriptSource)
+ throws ScriptException {
+ return inlineScript(DEFAULT_ENGINE, scriptSource);
+ }
+
+}
\ No newline at end of file
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/TemplatedURLBuilder.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/TemplatedURLBuilder.java
new file mode 100644
index 0000000..ca3cf8d2
--- /dev/null
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/TemplatedURLBuilder.java
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.dc.http.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.utilities.java.support.velocity.Template;
+
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.exception.VelocityException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.escape.Escaper;
+import com.google.common.net.UrlEscapers;
+
+/**
+ * An {@link net.shibboleth.idp.attribute.resolver.dc.impl.ExecutableSearchBuilder} that generates the URL to
+ * request by evaluating a {@link Template} against the currently resolved attributes within an
+ * {@link AttributeResolutionContext}.
+ */
+public class TemplatedURLBuilder extends AbstractHTTPSearchBuilder {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(TemplatedURLBuilder.class);
+
+ /** Template to be evaluated. */
+ @NonnullAfterInit private Template template;
+
+ /** Text of template to be evaluated. */
+ @NonnullAfterInit private String templateText;
+
+ /** VelocityEngine. */
+ @NonnullAfterInit private VelocityEngine engine;
+
+ /** Escaper for form parameters. */
+ @Nonnull private final Escaper paramEscaper;
+
+ /** Escaper for fragments. */
+ @Nonnull private final Escaper fragmentEscaper;
+
+ /** Escaper for path segments. */
+ @Nonnull private final Escaper pathEscaper;
+
+ /** Constructor. */
+ public TemplatedURLBuilder() {
+ paramEscaper = UrlEscapers.urlFormParameterEscaper();
+ fragmentEscaper = UrlEscapers.urlFragmentEscaper();
+ pathEscaper = UrlEscapers.urlPathSegmentEscaper();
+ }
+
+ /**
+ * Get the template to be evaluated.
+ *
+ * @return template
+ */
+ @NonnullAfterInit public Template getTemplate() {
+ return template;
+ }
+
+ /**
+ * Get the template text to be evaluated.
+ *
+ * @return template text
+ */
+ @NonnullAfterInit public String getTemplateText() {
+ return templateText;
+ }
+
+ /**
+ * Set the template to be evaluated.
+ *
+ * @param text template to be evaluated
+ */
+ public void setTemplateText(@Nullable final String text) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ templateText = StringSupport.trimOrNull(text);
+ }
+
+ /**
+ * Get the {@link VelocityEngine} to be used.
+ *
+ * @return template engine
+ */
+ @NonnullAfterInit public VelocityEngine getVelocityEngine() {
+ return engine;
+ }
+
+ /**
+ * Set the {@link VelocityEngine} to be used.
+ *
+ * @param velocityEngine engine to be used
+ */
+ public void setVelocityEngine(@Nonnull final VelocityEngine velocityEngine) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ engine = Constraint.isNotNull(velocityEngine, "Velocity engine cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (null == engine) {
+ throw new ComponentInitializationException("Velocity engine cannot be null");
+ }
+
+ if (null == templateText) {
+ throw new ComponentInitializationException("Template text cannot be null");
+ }
+
+ template = Template.fromTemplate(engine, templateText);
+ }
+
+ /**
+ * Invokes {@link Template#merge(org.apache.velocity.context.Context)} on the supplied context.
+ *
+ * @param context to merge
+ *
+ * @return result of the merge operation
+ */
+ @Nonnull @NotEmpty protected String merge(@Nonnull final VelocityContext context) {
+ final String result = template.merge(context);
+ log.debug("Template text {} yields {}", templateText, result);
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull protected String getURL(@Nonnull final AttributeResolutionContext resolutionContext,
+ @Nonnull final Map<String, List<IdPAttributeValue<?>>> dependencyAttributes) throws ResolutionException {
+
+ final VelocityContext context = new VelocityContext();
+ log.trace("Creating request URL using attribute resolution context {}", resolutionContext);
+ context.put("resolutionContext", resolutionContext);
+
+ context.put("paramEscaper", paramEscaper);
+ context.put("fragmentEscaper", fragmentEscaper);
+ context.put("pathEscaper", pathEscaper);
+
+ // inject dependencies
+ if (dependencyAttributes != null && !dependencyAttributes.isEmpty()) {
+ for (final Map.Entry<String, List<IdPAttributeValue<?>>> entry : dependencyAttributes.entrySet()) {
+ final List<Object> values = new ArrayList<>(entry.getValue().size());
+ for (final IdPAttributeValue<?> value : entry.getValue()) {
+ values.add(value.getValue());
+ }
+ log.trace("Adding dependency {} to context with {} value(s)", entry.getKey(), values.size());
+ context.put(entry.getKey(), values);
+ }
+ }
+
+ try {
+ return merge(context);
+ } catch (final VelocityException e) {
+ log.error("Error running template engine", e);
+ throw new ResolutionException("Error running template engine", e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/package-info.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/package-info.java
new file mode 100644
index 0000000..195491a
--- /dev/null
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A {@link net.shibboleth.idp.attribute.resolver.DataConnector} implementation that
+ * reads data from an HTTP web service.
+ */
+package net.shibboleth.idp.attribute.resolver.dc.http.impl;
\ No newline at end of file
diff --git a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/HTTPDataConnectorTest.java b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/HTTPDataConnectorTest.java
new file mode 100644
index 0000000..054f545
--- /dev/null
+++ b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/http/impl/HTTPDataConnectorTest.java
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.attribute.resolver.dc.http.impl;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.script.ScriptException;
+
+import net.shibboleth.ext.spring.resource.ResourceHelper;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.attribute.resolver.dc.impl.TestCache;
+import net.shibboleth.idp.saml.impl.TestSources;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.repository.RepositorySupport;
+import net.shibboleth.utilities.java.support.velocity.VelocityEngine;
+
+import org.apache.http.HttpStatus;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.opensaml.security.httpclient.HttpClientSecurityParameters;
+import org.opensaml.security.httpclient.impl.SecurityEnhancedHttpClientSupport;
+import org.springframework.core.io.ClassPathResource;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * Tests for {@link HTTPDataConnector}
+ */
+public class HTTPDataConnectorTest {
+
+ private static final String TEST_CONNECTOR_NAME = "HTTPConnector";
+
+ private static final String TEST_URL =
+ RepositorySupport.buildHTTPSResourceURL("java-identity-provider",
+ "idp-attribute-resolver-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/impl/dc/http/test.json");
+
+ private static final String SCRIPT_PATH = "/net/shibboleth/idp/attribute/resolver/impl/dc/http/test.js";
+
+ private HTTPDataConnector connector;
+
+ @BeforeMethod public void setUp() throws IOException, ScriptException {
+ connector = new HTTPDataConnector();
+ connector.setId(TEST_CONNECTOR_NAME);
+ connector.setHttpClient(
+ HttpClientBuilder.create()
+ .setSSLSocketFactory(SecurityEnhancedHttpClientSupport.buildTLSSocketFactory(false, false))
+ .build());
+
+ final ScriptedResponseMappingStrategy mapping =
+ ScriptedResponseMappingStrategy.resourceScript(ResourceHelper.of(new ClassPathResource(SCRIPT_PATH)));
+ mapping.setLogPrefix(TEST_CONNECTOR_NAME + ":");
+ connector.setMappingStrategy(mapping);
+ }
+
+ @Test public void test() throws ComponentInitializationException, ResolutionException {
+ final HttpClientSecurityParameters params = new HttpClientSecurityParameters();
+ params.setTLSProtocols(Collections.singleton("TLSv1"));
+ final TemplatedURLBuilder builder = new TemplatedURLBuilder();
+ builder.setTemplateText(TEST_URL);
+ builder.setVelocityEngine(VelocityEngine.newVelocityEngine());
+ builder.setHttpClientSecurityParameters(params);
+ builder.initialize();
+
+ connector.setExecutableSearchBuilder(builder);
+ ((ScriptedResponseMappingStrategy) connector.getMappingStrategy()).setAcceptStatuses(Collections.singleton(HttpStatus.SC_OK));
+ ((ScriptedResponseMappingStrategy) connector.getMappingStrategy()).setAcceptTypes(Collections.singleton("application/json"));
+ connector.initialize();
+
+ final AttributeResolutionContext context =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+
+ final Map<String,IdPAttribute> attrs = connector.resolve(context);
+
+ Assert.assertEquals(attrs.size(), 2);
+
+ Assert.assertEquals(attrs.get("foo").getValues().size(), 1);
+ Assert.assertEquals(attrs.get("foo").getValues().get(0).getValue(), "foo1");
+
+ Assert.assertEquals(attrs.get("bar").getValues().size(), 2);
+ Assert.assertEquals(attrs.get("bar").getValues().get(0).getValue(), "bar1");
+ Assert.assertEquals(attrs.get("bar").getValues().get(1).getValue(), "bar2");
+ }
+
+ @Test(expectedExceptions=ResolutionException.class) public void testBadProtocol()
+ throws ComponentInitializationException, ResolutionException {
+ final HttpClientSecurityParameters params = new HttpClientSecurityParameters();
+ params.setTLSProtocols(Collections.singleton("SSLv3"));
+ final TemplatedURLBuilder builder = new TemplatedURLBuilder();
+ builder.setTemplateText(TEST_URL);
+ builder.setVelocityEngine(VelocityEngine.newVelocityEngine());
+ builder.setHttpClientSecurityParameters(params);
+ builder.initialize();
+
+ connector.setExecutableSearchBuilder(builder);
+ connector.initialize();
+
+ final AttributeResolutionContext context =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+
+ connector.resolve(context);
+ }
+
+ @Test(expectedExceptions=ResolutionException.class) public void testMissing()
+ throws ComponentInitializationException, ResolutionException {
+ final TemplatedURLBuilder builder = new TemplatedURLBuilder();
+ builder.setTemplateText("https://shibboleth.net/test.json");
+ builder.setVelocityEngine(VelocityEngine.newVelocityEngine());
+ builder.initialize();
+
+ connector.setExecutableSearchBuilder(builder);
+ connector.initialize();
+
+ final AttributeResolutionContext context =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+
+ connector.resolve(context);
+ }
+
+ @Test public void testMissingOk() throws ComponentInitializationException, ResolutionException, ScriptException {
+ final TemplatedURLBuilder builder = new TemplatedURLBuilder();
+ builder.setTemplateText("https://shibboleth.net/test.json");
+ builder.setVelocityEngine(VelocityEngine.newVelocityEngine());
+ builder.initialize();
+
+ connector.setExecutableSearchBuilder(builder);
+
+ final ScriptedResponseMappingStrategy mapping = ScriptedResponseMappingStrategy.inlineScript("1");
+ mapping.setLogPrefix(TEST_CONNECTOR_NAME + ":");
+ mapping.setAcceptStatuses(Collections.singleton(HttpStatus.SC_NOT_FOUND));
+ connector.setMappingStrategy(mapping);
+
+ connector.initialize();
+
+ final AttributeResolutionContext context =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+
+ final Map<String,IdPAttribute> attrs = connector.resolve(context);
+
+ Assert.assertTrue(attrs == null || attrs.isEmpty());
+ }
+
+ @Test public void resolveWithCache() throws ComponentInitializationException, ResolutionException {
+ final TemplatedURLBuilder builder = new TemplatedURLBuilder();
+ builder.setTemplateText(TEST_URL);
+ builder.setVelocityEngine(VelocityEngine.newVelocityEngine());
+ builder.initialize();
+
+ connector.setExecutableSearchBuilder(builder);
+
+ final TestCache cache = new TestCache();
+ connector.setResultsCache(cache);
+ connector.initialize();
+
+ final AttributeResolutionContext context =
+ TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
+ TestSources.SP_ENTITY_ID);
+
+ Assert.assertTrue(cache.size() == 0);
+ final Map<String,IdPAttribute> optional = connector.resolve(context);
+ Assert.assertTrue(cache.size() == 1);
+ Assert.assertEquals(cache.iterator().next(), optional);
+ }
+
+}
\ No newline at end of file
diff --git a/idp-attribute-resolver-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/impl/dc/http/test.js b/idp-attribute-resolver-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/impl/dc/http/test.js
new file mode 100644
index 0000000..1c1fbe2
--- /dev/null
+++ b/idp-attribute-resolver-impl/src/test/resources/net/shibboleth/idp/attribute/resolver/impl/dc/http/test.js
@@ -0,0 +1,20 @@
+var EntityHelper = Java.type("org.apache.http.util.EntityUtils");
+var body = EntityHelper.toString(response.getEntity(), "UTF-8");
+var result = JSON.parse(body.toString("UTF-8"));
+
+var IdPAttribute = Java.type("net.shibboleth.idp.attribute.IdPAttribute");
+var StringValue = Java.type("net.shibboleth.idp.attribute.StringAttributeValue");
+var HashSet = Java.type("java.util.HashSet");
+
+for (var i=0; i<result.length; i++) {
+
+ var attr = new IdPAttribute(result[i].name);
+ var values = new HashSet();
+
+ for (var j=0; j<result[i].values.length; j++) {
+ values.add(new StringValue(result[i].values[j]));
+ }
+
+ attr.setValues(values);
+ connectorResults.add(attr);
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list