Skip to content

Commit

Permalink
KNOX-2390 - Let end-users configure SAML2 client configuration using …
Browse files Browse the repository at this point in the history
…Pac4J provider parameters (#348)
  • Loading branch information
smolnar82 authored Jun 19, 2020
1 parent d10e15d commit fe0d0e7
Show file tree
Hide file tree
Showing 8 changed files with 369 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 org.apache.knox.gateway.pac4j.config;

import java.util.List;
import java.util.Map;

import org.pac4j.core.client.Client;
import org.pac4j.core.http.callback.PathParameterCallbackUrlResolver;
import org.pac4j.oidc.client.AzureAdClient;

public class AzureADClientConfigurationDecorator implements ClientConfigurationDecorator {
private static final String AZURE_AD_CLIENT_CLASS_NAME = AzureAdClient.class.getSimpleName();

@Override
public void decorateClients(List<Client> clients, Map<String, String> properties) {
for (Client client : clients) {
if (AZURE_AD_CLIENT_CLASS_NAME.equalsIgnoreCase(client.getName())) {
// special handling for Azure AD, use path separators instead of query params
((AzureAdClient) client).setCallbackUrlResolver(new PathParameterCallbackUrlResolver());
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 org.apache.knox.gateway.pac4j.config;

import java.util.List;
import java.util.Map;

import org.pac4j.core.client.Client;

/**
* Defines the contract of decorating different type of Pac4J client configurations
*/
public interface ClientConfigurationDecorator {

/**
* Decorates the given clients' configuration using the given properties (if applicable)
*
* @param clients
* the client, whose configuration should be decorated
* @param properties
* the properties which may contain the required information to decorate the clients
*/
void decorateClients(List<Client> clients, Map<String, String> properties);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 org.apache.knox.gateway.pac4j.config;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.pac4j.core.client.Client;

public class Pac4jClientConfigurationDecorator implements ClientConfigurationDecorator {

private static final List<ClientConfigurationDecorator> DEFAULT_DECORATORS = Arrays.asList(new SAML2ClientConfigurationDecorator(), new AzureADClientConfigurationDecorator());
private final List<ClientConfigurationDecorator> decorators;

public Pac4jClientConfigurationDecorator() {
this(DEFAULT_DECORATORS);
}

// package protected so that it's visible in unit tests
Pac4jClientConfigurationDecorator(List<ClientConfigurationDecorator> decorators) {
this.decorators = decorators;
}

@Override
public void decorateClients(List<Client> clients, Map<String, String> properties) {
decorators.forEach(decorator -> decorator.decorateClients(clients, properties));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 org.apache.knox.gateway.pac4j.config;

import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.pac4j.core.client.Client;
import org.pac4j.saml.client.SAML2Client;

public class SAML2ClientConfigurationDecorator implements ClientConfigurationDecorator {

private static final String SAML2_CLIENT_CLASS_NAME = SAML2Client.class.getSimpleName();
private static final String CONFIG_NAME_USE_NAME_QUALIFIER = "useNameQualifier";
private static final String CONFIG_NAME_USE_FORCE_AUTH = "forceAuth";
private static final String CONFIG_NAME_USE_PASSIVE = "passive";
private static final String CONFIG_NAME_NAMEID_POLICY_FORMAT = "nameIdPolicyFormat";

@Override
public void decorateClients(List<Client> clients, Map<String, String> properties) {
for (Client client : clients) {
if (SAML2_CLIENT_CLASS_NAME.equalsIgnoreCase(client.getName())) {
final SAML2Client saml2Client = (SAML2Client) client;
setUseNameQualifierFlag(properties, saml2Client);
setForceAuthFlag(properties, saml2Client);
setPassiveFlag(properties, saml2Client);
setNameIdPolicyFormat(properties, saml2Client);
}
}
}

private void setUseNameQualifierFlag(Map<String, String> properties, final SAML2Client saml2Client) {
final String useNameQualifier = properties.get(CONFIG_NAME_USE_NAME_QUALIFIER);
if (StringUtils.isNotBlank(useNameQualifier)) {
saml2Client.getConfiguration().setUseNameQualifier(Boolean.valueOf(useNameQualifier));
}
}

private void setForceAuthFlag(Map<String, String> properties, final SAML2Client saml2Client) {
final String forceAuth = properties.get(CONFIG_NAME_USE_FORCE_AUTH);
if (StringUtils.isNotBlank(forceAuth)) {
saml2Client.getConfiguration().setForceAuth(Boolean.valueOf(forceAuth));
}
}

private void setPassiveFlag(Map<String, String> properties, final SAML2Client saml2Client) {
final String passive = properties.get(CONFIG_NAME_USE_PASSIVE);
if (StringUtils.isNotBlank(passive)) {
saml2Client.getConfiguration().setPassive(Boolean.valueOf(passive));
}
}

private void setNameIdPolicyFormat(Map<String, String> properties, final SAML2Client saml2Client) {
final String nameIdPolicyFormat = properties.get(CONFIG_NAME_NAMEID_POLICY_FORMAT);
if (StringUtils.isNotBlank(nameIdPolicyFormat)) {
saml2Client.getConfiguration().setNameIdPolicyFormat(nameIdPolicyFormat);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.pac4j.Pac4jMessages;
import org.apache.knox.gateway.pac4j.config.ClientConfigurationDecorator;
import org.apache.knox.gateway.pac4j.config.Pac4jClientConfigurationDecorator;
import org.apache.knox.gateway.pac4j.session.KnoxSessionStore;
import org.apache.knox.gateway.services.ServiceType;
import org.apache.knox.gateway.services.GatewayServices;
Expand All @@ -34,7 +36,6 @@
import org.pac4j.core.config.Config;
import org.pac4j.core.context.session.J2ESessionStore;
import org.pac4j.core.context.session.SessionStore;
import org.pac4j.core.http.callback.PathParameterCallbackUrlResolver;
import org.pac4j.core.util.CommonHelper;
import org.pac4j.http.client.indirect.IndirectBasicAuthClient;
import org.pac4j.http.credentials.authenticator.test.SimpleTestUsernamePasswordAuthenticator;
Expand Down Expand Up @@ -74,6 +75,8 @@ public class Pac4jDispatcherFilter implements Filter {

private static Pac4jMessages log = MessagesFactory.get(Pac4jMessages.class);

private static final ClientConfigurationDecorator PAC4J_CLIENT_CONFIGURATION_DECORATOR = new Pac4jClientConfigurationDecorator();

public static final String TEST_BASIC_AUTH = "testBasicAuth";

public static final String PAC4J_CALLBACK_URL = "pac4j.callbackUrl";
Expand Down Expand Up @@ -181,19 +184,11 @@ public void init( FilterConfig filterConfig ) throws ServletException {
log.atLeastOnePac4jClientMustBeDefined();
throw new ServletException("At least one pac4j client must be defined.");
}
if (CommonHelper.isBlank(clientNameParameter)) {
clientName = clients.get(0).getName();
} else {
clientName = clientNameParameter;
}

/* special handling for Azure AD, use path separators instead of query params */
clients.forEach( client -> {
if(client.getName().equalsIgnoreCase(AzureAdClient.class.getSimpleName())) {
((AzureAdClient)client).setCallbackUrlResolver(new PathParameterCallbackUrlResolver());
}
});
clientName = CommonHelper.isBlank(clientNameParameter) ? clients.get(0).getName() : clientNameParameter;

//decorating client configuration (if needed)
PAC4J_CLIENT_CONFIGURATION_DECORATOR.decorateClients(clients, properties);
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 org.apache.knox.gateway.pac4j.config;

import static org.junit.Assert.assertTrue;

import java.util.Collections;

import org.junit.Test;
import org.pac4j.core.http.callback.PathParameterCallbackUrlResolver;
import org.pac4j.oidc.client.AzureAdClient;
import org.pac4j.oidc.config.AzureAdOidcConfiguration;

public class AzureADClientConfigurationDecoratorTest {

@Test
public void testAzureADClientConfigurationDecoration() throws Exception {
final AzureAdOidcConfiguration azureAdConfig = new AzureAdOidcConfiguration();
final AzureAdClient client = new AzureAdClient(azureAdConfig);
final AzureADClientConfigurationDecorator azureConfigDecorator = new AzureADClientConfigurationDecorator();
azureConfigDecorator.decorateClients(Collections.singletonList(client), null);
assertTrue(client.getCallbackUrlResolver() instanceof PathParameterCallbackUrlResolver);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 org.apache.knox.gateway.pac4j.config;

import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.easymock.EasyMock;
import org.junit.Test;
import org.pac4j.core.client.Client;

public class Pac4jClientConfigurationDecoratorTest {

@Test
public void testClientConfigDecoration() throws Exception {
final AtomicInteger tested = new AtomicInteger(0);
final AtomicInteger decorated = new AtomicInteger(0);

final ClientConfigurationDecorator passiveDecorator = new TestClientConfigurationDecorator(tested, decorated, false);
final ClientConfigurationDecorator activeDecorator = new TestClientConfigurationDecorator(tested, decorated, true);
final Pac4jClientConfigurationDecorator pac4jConfigurationDecorator = new Pac4jClientConfigurationDecorator(Arrays.asList(passiveDecorator, activeDecorator));
final Client client = EasyMock.createNiceMock(Client.class);
pac4jConfigurationDecorator.decorateClients(Collections.singletonList(client), null);
assertEquals(2, tested.get());
assertEquals(1, decorated.get());
}

private static class TestClientConfigurationDecorator implements ClientConfigurationDecorator {

private final AtomicInteger tested;
private final AtomicInteger decorated;
private final boolean decorate;

TestClientConfigurationDecorator(AtomicInteger testedClientsNum, AtomicInteger decoratedClientsNum, boolean decorate) {
this.tested = testedClientsNum;
this.decorated = decoratedClientsNum;
this.decorate = decorate;
}

@Override
public void decorateClients(List<Client> clients, Map<String, String> properties) {
clients.forEach(client -> {
tested.incrementAndGet();
if (decorate) {
decorated.incrementAndGet();
}
});
}
}

}
Loading

0 comments on commit fe0d0e7

Please sign in to comment.