diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/token/AccessTokenClient.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/token/AccessTokenClient.java index a06cc17..f3e0b75 100644 --- a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/token/AccessTokenClient.java +++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/token/AccessTokenClient.java @@ -4,21 +4,20 @@ import com.fasterxml.jackson.core.JsonParseException; 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.LinkedList; import java.util.List; -import java.util.Map; import java.util.function.Consumer; import org.alfresco.util.ParameterCheck; +import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; +import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicNameValuePair; import org.keycloak.OAuth2Constants; import org.keycloak.TokenVerifier; @@ -38,6 +37,7 @@ import org.keycloak.util.JsonSerialization; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import de.acosix.alfresco.keycloak.repo.util.NameValueMapAdapter; import de.acosix.alfresco.keycloak.repo.util.RefreshableAccessTokenHolder; /** @@ -284,20 +284,20 @@ public class AccessTokenClient final HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(this.deployment.getAuthServerBaseUrl()) .path(ServiceUrlConstants.TOKEN_PATH).build(this.deployment.getRealm())); - final List formParams = new ArrayList<>(); + final List formParams = new LinkedList<>(); postParamProvider.accept(formParams); - Map formMap = new HashMap<>(); - for (NameValuePair pair : formParams) - formMap.put(pair.getName(), pair.getValue()); + final List
headers = new LinkedList<>(); ClientCredentialsProviderUtils.setClientCredentials( this.deployment.getAdapterConfig(), this.deployment.getClientAuthenticator(), - Collections.emptyMap(), - formMap); + new NameValueMapAdapter<>(headers, BasicHeader.class), + new NameValueMapAdapter<>(formParams, BasicNameValuePair.class)); + for (Header header : headers) + post.addHeader(header); final UrlEncodedFormEntity form = new UrlEncodedFormEntity(formParams, "UTF-8"); post.setEntity(form); diff --git a/repository/src/main/java/de/acosix/alfresco/keycloak/repo/util/NameValueMapAdapter.java b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/util/NameValueMapAdapter.java new file mode 100644 index 0000000..cb13219 --- /dev/null +++ b/repository/src/main/java/de/acosix/alfresco/keycloak/repo/util/NameValueMapAdapter.java @@ -0,0 +1,151 @@ +package de.acosix.alfresco.keycloak.repo.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.apache.http.NameValuePair; + +public class NameValueMapAdapter implements Map { + + private final List pairs; + private final Class type; + + public NameValueMapAdapter(List pairs, Class type) { + this.pairs = pairs; + this.type = type; + } + + @Override + public void clear() { + this.pairs.clear(); + } + + @Override + public boolean containsKey(Object key) { + for (NameValuePair pair : this.pairs) + if (pair.getName().equals(key)) + return true; + return false; + } + + @Override + public boolean containsValue(Object value) { + for (NameValuePair pair : this.pairs) + if (pair.getValue().equals(value)) + return true; + return false; + } + + @Override + public Set> entrySet() { + Set> set = new HashSet>(); + for (NameValuePair pair : this.pairs) { + set.add(new Entry() { + @Override + public String getKey() { + return pair.getName(); + } + + @Override + public String getValue() { + return pair.getValue(); + } + + @Override + public String setValue(String value) { + throw new UnsupportedOperationException(); + } + }); + } + + return set; + } + + @Override + public String get(Object key) { + for (NameValuePair pair : this.pairs) + if (pair.getName().equals(key)) + return pair.getValue(); + return null; + } + + @Override + public boolean isEmpty() { + return this.pairs.isEmpty(); + } + + @Override + public Set keySet() { + Set set = new HashSet<>(); + for (NameValuePair pair : this.pairs) + set.add(pair.getName()); + return set; + } + + @Override + public String put(String key, String value) { + ListIterator i = (ListIterator) this.pairs.listIterator(); + while (i.hasNext()) { + NameValuePair pair = i.next(); + if (pair.getName().equals(key)) { + i.remove(); + i.add(this.newNameValuePair(key, value)); + return pair.getValue(); + } + } + + i.add(this.newNameValuePair(key, value)); + return null; + } + + @Override + public void putAll(Map m) { + for (Entry e : m.entrySet()) + this.put(e.getKey(), e.getValue()); + } + + @Override + public String remove(Object key) { + ListIterator i = (ListIterator) this.pairs.listIterator(); + while (i.hasNext()) { + NameValuePair pair = i.next(); + if (pair.getName().equals(key)) { + i.remove(); + return pair.getValue(); + } + } + + return null; + } + + @Override + public int size() { + return this.pairs.size(); + } + + @Override + public Collection values() { + List list = new ArrayList<>(this.pairs.size()); + for (NameValuePair pair : this.pairs) + list.add(pair.getValue()); + return list; + } + + private T newNameValuePair(String key, String value) { + try { + Constructor constructor = this.type.getConstructor(String.class, String.class); + return constructor.newInstance(key, value); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new AlfrescoRuntimeException(e.getMessage(), e); + } + } + +} \ No newline at end of file diff --git a/share/src/main/java/de/acosix/alfresco/keycloak/share/util/NameValueMapAdapter.java b/share/src/main/java/de/acosix/alfresco/keycloak/share/util/NameValueMapAdapter.java new file mode 100644 index 0000000..fcf494b --- /dev/null +++ b/share/src/main/java/de/acosix/alfresco/keycloak/share/util/NameValueMapAdapter.java @@ -0,0 +1,151 @@ +package de.acosix.alfresco.keycloak.share.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.apache.http.NameValuePair; + +public class NameValueMapAdapter implements Map { + + private final List pairs; + private final Class type; + + public NameValueMapAdapter(List pairs, Class type) { + this.pairs = pairs; + this.type = type; + } + + @Override + public void clear() { + this.pairs.clear(); + } + + @Override + public boolean containsKey(Object key) { + for (NameValuePair pair : this.pairs) + if (pair.getName().equals(key)) + return true; + return false; + } + + @Override + public boolean containsValue(Object value) { + for (NameValuePair pair : this.pairs) + if (pair.getValue().equals(value)) + return true; + return false; + } + + @Override + public Set> entrySet() { + Set> set = new HashSet>(); + for (NameValuePair pair : this.pairs) { + set.add(new Entry() { + @Override + public String getKey() { + return pair.getName(); + } + + @Override + public String getValue() { + return pair.getValue(); + } + + @Override + public String setValue(String value) { + throw new UnsupportedOperationException(); + } + }); + } + + return set; + } + + @Override + public String get(Object key) { + for (NameValuePair pair : this.pairs) + if (pair.getName().equals(key)) + return pair.getValue(); + return null; + } + + @Override + public boolean isEmpty() { + return this.pairs.isEmpty(); + } + + @Override + public Set keySet() { + Set set = new HashSet<>(); + for (NameValuePair pair : this.pairs) + set.add(pair.getName()); + return set; + } + + @Override + public String put(String key, String value) { + ListIterator i = (ListIterator) this.pairs.listIterator(); + while (i.hasNext()) { + NameValuePair pair = i.next(); + if (pair.getName().equals(key)) { + i.remove(); + i.add(this.newNameValuePair(key, value)); + return pair.getValue(); + } + } + + i.add(this.newNameValuePair(key, value)); + return null; + } + + @Override + public void putAll(Map m) { + for (Entry e : m.entrySet()) + this.put(e.getKey(), e.getValue()); + } + + @Override + public String remove(Object key) { + ListIterator i = (ListIterator) this.pairs.listIterator(); + while (i.hasNext()) { + NameValuePair pair = i.next(); + if (pair.getName().equals(key)) { + i.remove(); + return pair.getValue(); + } + } + + return null; + } + + @Override + public int size() { + return this.pairs.size(); + } + + @Override + public Collection values() { + List list = new ArrayList<>(this.pairs.size()); + for (NameValuePair pair : this.pairs) + list.add(pair.getValue()); + return list; + } + + private T newNameValuePair(String key, String value) { + try { + Constructor constructor = this.type.getConstructor(String.class, String.class); + return constructor.newInstance(key, value); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new AlfrescoRuntimeException(e.getMessage(), e); + } + } + +} \ No newline at end of file diff --git a/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java b/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java index 7dbde98..7842f4b 100644 --- a/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java +++ b/share/src/main/java/de/acosix/alfresco/keycloak/share/web/KeycloakAuthenticationFilter.java @@ -26,10 +26,9 @@ import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.function.BiFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -50,6 +49,7 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.util.EqualsHelper; import org.alfresco.util.PropertyCheck; import org.alfresco.web.site.servlet.SSOAuthenticationFilter; +import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; @@ -60,6 +60,7 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.conn.params.ConnRouteParams; import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.HttpParams; import org.apache.http.util.EntityUtils; @@ -127,6 +128,7 @@ import de.acosix.alfresco.keycloak.share.config.KeycloakAdapterConfigElement; import de.acosix.alfresco.keycloak.share.config.KeycloakAuthenticationConfigElement; import de.acosix.alfresco.keycloak.share.config.KeycloakConfigConstants; import de.acosix.alfresco.keycloak.share.remote.AccessTokenAwareSlingshotAlfrescoConnector; +import de.acosix.alfresco.keycloak.share.util.NameValueMapAdapter; import de.acosix.alfresco.keycloak.share.util.RefreshableAccessTokenHolder; /** @@ -1728,7 +1730,7 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I final HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(this.keycloakDeployment.getAuthServerBaseUrl()) .path(ServiceUrlConstants.TOKEN_PATH).build(this.keycloakDeployment.getRealm())); - final List formParams = new ArrayList<>(); + final List formParams = new LinkedList<>(); formParams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)); formParams.add(new BasicNameValuePair(OAuth2Constants.AUDIENCE, alfrescoResourceName)); @@ -1751,16 +1753,16 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I "Either an active security context or access token should be present in the session, or previous validations have caught their non-existence and prevented this operation form being called"); } - Map formMap = new HashMap<>(); - for (NameValuePair formParam : formParams) - formMap.put(formParam.getName(), formParam.getValue()); + final List
headers = new LinkedList<>(); ClientCredentialsProviderUtils.setClientCredentials( this.keycloakDeployment.getAdapterConfig(), this.keycloakDeployment.getClientAuthenticator(), - Collections.emptyMap(), - formMap); + new NameValueMapAdapter<>(headers, BasicHeader.class), + new NameValueMapAdapter<>(formParams, BasicNameValuePair.class)); + for (Header header : headers) + post.addHeader(header); final UrlEncodedFormEntity form = new UrlEncodedFormEntity(formParams, "UTF-8"); post.setEntity(form);