FEDIZ-172 - OIDC DataProvider should support client_credentials clients
diff --git a/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/OAuthDataProviderImpl.java b/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/OAuthDataProviderImpl.java
index 16bd697..43bd6de 100644
--- a/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/OAuthDataProviderImpl.java
+++ b/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/OAuthDataProviderImpl.java
@@ -21,7 +21,16 @@
import java.security.Principal;
import java.util.Collections;
import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.interceptor.security.NamePasswordCallbackHandler;
import org.apache.cxf.rs.security.oauth2.common.Client;
import org.apache.cxf.rs.security.oauth2.common.OAuthPermission;
import org.apache.cxf.rs.security.oauth2.grants.code.DefaultEHCacheCodeDataProvider;
@@ -31,7 +40,12 @@
public class OAuthDataProviderImpl extends DefaultEHCacheCodeDataProvider {
+ private static final Logger LOG = LogUtils.getL7dLogger(OAuthDataProviderImpl.class);
+
private boolean checkOnlyRegisteredClients;
+ private String contextName;
+ private Configuration loginConfig;
+
@Override
public Client getClient(String clientId) {
@@ -76,13 +90,44 @@
}
protected Client authenticateClient(String clientId, String clientSecret) {
- // If the authentication is successful:
- // return new Client(clientId, clientSecret, true)
+ if (contextName != null) {
+ try {
+ // Login using JAAS
+ CallbackHandler callbackHandler =
+ new NamePasswordCallbackHandler(clientId, clientSecret);
+ LoginContext ctx = new LoginContext(getContextName(), null, callbackHandler, loginConfig);
+ ctx.login();
+ Client client = new Client(clientId, clientSecret, true);
+ client.setAllowedGrantTypes(Collections.singletonList(OAuthConstants.CLIENT_CREDENTIALS_GRANT));
+ ctx.logout();
+ return client;
+ } catch (LoginException ex) {
+ ex.printStackTrace();
+ String errorMessage = "Authentication failed: " + ex.getMessage();
+ LOG.log(Level.FINE, errorMessage, ex);
+ }
+ }
return null;
}
public void setCheckOnlyRegisteredClients(boolean checkOnlyRegisteredClients) {
this.checkOnlyRegisteredClients = checkOnlyRegisteredClients;
}
+
+ public String getContextName() {
+ return contextName;
+ }
+
+ public void setContextName(String contextName) {
+ this.contextName = contextName;
+ }
+
+ public Configuration getLoginConfig() {
+ return loginConfig;
+ }
+
+ public void setLoginConfig(Configuration loginConfig) {
+ this.loginConfig = loginConfig;
+ }
}
diff --git a/systests/oidc/pom.xml b/systests/oidc/pom.xml
index 5639f37..57f3e69 100644
--- a/systests/oidc/pom.xml
+++ b/systests/oidc/pom.xml
@@ -99,6 +99,16 @@
<version>${cxf.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-ws-security</artifactId>
+ <version>${cxf.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-ws-policy</artifactId>
+ <version>${cxf.version}</version>
+ </dependency>
</dependencies>
<build>
<testResources>
@@ -197,6 +207,17 @@
</artifactItems>
</configuration>
</execution>
+ <execution>
+ <id>copy-extra-jars-to-oidc</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${basedir}/target/tomcat/rp/webapps/fediz-oidc/WEB-INF/lib</outputDirectory>
+ <includeScope>compile</includeScope>
+ </configuration>
+ </execution>
</executions>
</plugin>
<plugin>
@@ -222,6 +243,26 @@
</resources>
</configuration>
</execution>
+ <execution>
+ <id>copy-entities-to-oidc</id>
+ <phase>generate-test-sources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${basedir}/target/tomcat/rp/webapps/fediz-oidc/WEB-INF</outputDirectory>
+ <resources>
+ <resource>
+ <directory>${basedir}/src/test/resources/oidc</directory>
+ <includes>
+ <include>applicationContext.xml</include>
+ <include>data-manager.xml</include>
+ </includes>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
</executions>
</plugin>
<plugin>
@@ -261,6 +302,7 @@
<idp.https.port>${idp.https.port}</idp.https.port>
<rp.https.port>${rp.https.port}</rp.https.port>
<java.io.tmpdir>${basedir}/target</java.io.tmpdir>
+ <java.security.auth.login.config>src/test/resources/sts.jaas</java.security.auth.login.config>
</systemPropertyVariables>
<includes>
<include>**/systests/**</include>
diff --git a/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/OIDCTest.java b/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/OIDCTest.java
index ccb3bc9..627e44e 100644
--- a/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/OIDCTest.java
+++ b/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/OIDCTest.java
@@ -632,6 +632,27 @@
webClient.close();
}
+ @org.junit.Test
+ public void testClientCredentialsSTS() throws Exception {
+ String url = "https://localhost:" + getRpHttpsPort() + "/fediz-oidc/oauth2/token";
+ WebRequest request = new WebRequest(new URL(url), HttpMethod.POST);
+
+ request.setRequestParameters(new ArrayList<NameValuePair>());
+ request.getRequestParameters().add(new NameValuePair("client_id", "alice"));
+ request.getRequestParameters().add(new NameValuePair("client_secret", "ecila"));
+ request.getRequestParameters().add(new NameValuePair("grant_type", "client_credentials"));
+
+ final WebClient webClient = new WebClient();
+ webClient.getOptions().setUseInsecureSSL(true);
+ webClient.getOptions().setJavaScriptEnabled(false);
+ final UnexpectedPage responsePage = webClient.getPage(request);
+ String response = responsePage.getWebResponse().getContentAsString();
+
+ Assert.assertTrue(response.contains("access_token"));
+
+ webClient.close();
+ }
+
private static WebClient setupWebClient(String user, String password, String idpPort) {
final WebClient webClient = new WebClient();
webClient.getOptions().setUseInsecureSSL(true);
diff --git a/systests/oidc/src/test/resources/oidc/applicationContext.xml b/systests/oidc/src/test/resources/oidc/applicationContext.xml
new file mode 100644
index 0000000..2eefe27
--- /dev/null
+++ b/systests/oidc/src/test/resources/oidc/applicationContext.xml
@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:cxf="http://cxf.apache.org/core"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:jaxrs="http://cxf.apache.org/jaxrs"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xmlns:http="http://cxf.apache.org/transports/http/configuration"
+ xmlns:sec="http://cxf.apache.org/configuration/security"
+ xsi:schemaLocation="
+ http://cxf.apache.org/core
+ http://cxf.apache.org/schemas/core.xsd
+ http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://cxf.apache.org/jaxrs
+ http://cxf.apache.org/schemas/jaxrs.xsd
+ http://www.springframework.org/schema/util
+ http://www.springframework.org/schema/util/spring-util.xsd
+ http://cxf.apache.org/transports/http/configuration
+ http://cxf.apache.org/schemas/configuration/http-conf.xsd
+ http://cxf.apache.org/configuration/security
+ http://cxf.apache.org/schemas/configuration/security.xsd">
+
+ <cxf:bus>
+ <cxf:features>
+ <cxf:logging/>
+ </cxf:features>
+ </cxf:bus>
+
+ <import resource="data-manager.xml" />
+
+ <!-- Supports OIDC Authorization Code flow -->
+ <bean id="oidcAuthorizationService" class="org.apache.cxf.rs.security.oidc.idp.OidcAuthorizationCodeService">
+ <property name="dataProvider" ref="oauthProvider"/>
+ <property name="subjectCreator" ref="subjectCreator"/>
+ <property name="skipAuthorizationWithOidcScope" value="true"/>
+ <!--
+ <property name="useAllClientScopes" value="true"/>
+ -->
+ <property name="canSupportPublicClients" value="true"/>
+ </bean>
+ <!-- Supports OIDC Implicit and Hybrid flows -->
+ <bean id="oidcHybridService" class="org.apache.cxf.rs.security.oidc.idp.OidcHybridService">
+ <property name="dataProvider" ref="oauthProvider"/>
+ <property name="subjectCreator" ref="subjectCreator"/>
+ <property name="skipAuthorizationWithOidcScope" value="true"/>
+ <property name="responseFilter" ref="idTokenFilter"/>
+ <property name="codeService" ref="oidcAuthorizationService"/>
+ </bean>
+
+ <util:list id="oidcServices">
+ <ref bean="oidcAuthorizationService"/>
+ <ref bean="oidcHybridService"/>
+ </util:list>
+
+ <!-- Service which makes Code, Implicit and Hybrid flow available
+ at the same relative "/authorize" address -->
+ <bean id="authorizationService" class="org.apache.cxf.rs.security.oauth2.services.AuthorizationService">
+ <property name="services" ref="oidcServices"/>
+ </bean>
+
+ <!-- Service supporting all OIDC Core flows -->
+ <jaxrs:server address="/idp">
+ <jaxrs:serviceBeans>
+ <ref bean="authorizationService"/>
+ </jaxrs:serviceBeans>
+ <jaxrs:providers>
+ <ref bean="viewProvider"/>
+ <ref bean="oauthJsonProvider"/>
+ </jaxrs:providers>
+ <jaxrs:properties>
+ <entry key="rs.security.signature.properties" value="rs.security.properties"/>
+ <entry key="rs.security.signature.key.password.provider" value-ref="keyPasswordProvider"/>
+ </jaxrs:properties>
+ </jaxrs:server>
+
+ <!--
+ Public JWK Key Service: Disable it if the client secret is used or if
+ pre-installing public OIDC keys to clients is preferred
+ -->
+ <bean id="oidcKeysService" class="org.apache.cxf.rs.security.oidc.idp.OidcKeysService"/>
+ <jaxrs:server address="/jwk">
+ <jaxrs:serviceBeans>
+ <ref bean="oidcKeysService"/>
+ </jaxrs:serviceBeans>
+ <jaxrs:providers>
+ <bean class="org.apache.cxf.rs.security.jose.jaxrs.JsonWebKeysProvider"/>
+ </jaxrs:providers>
+ <jaxrs:properties>
+ <entry key="rs.security.signature.properties" value="rs.security.properties"/>
+ <entry key="rs.security.signature.key.password.provider" value-ref="keyPasswordProvider"/>
+ </jaxrs:properties>
+ </jaxrs:server>
+
+
+ <bean id="oauth2TokenValidationFilter" class="org.apache.cxf.rs.security.oauth2.filters.OAuthRequestFilter">
+ <property name="dataProvider" ref="oauthProvider"/>
+ <property name="audienceIsEndpointAddress" value="false"/>
+ </bean>
+
+ <!-- User Info Service -->
+ <bean id="userInfoService" class="org.apache.cxf.rs.security.oidc.idp.UserInfoService">
+ <property name="oauthDataProvider" ref="oauthProvider"/>
+ <property name="jwsRequired" value="false"/>
+ </bean>
+ <jaxrs:server address="/users">
+ <jaxrs:serviceBeans>
+ <ref bean="userInfoService"/>
+ </jaxrs:serviceBeans>
+ <jaxrs:providers>
+ <bean class="org.apache.cxf.jaxrs.provider.json.JsonMapObjectProvider"/>
+ <ref bean="oauth2TokenValidationFilter"/>
+ </jaxrs:providers>
+ </jaxrs:server>
+
+ <bean id="keyPasswordProvider" class="org.apache.cxf.fediz.service.oidc.PrivateKeyPasswordProviderImpl"/>
+
+ <!-- Client Registration Service -->
+ <bean id="clientRegService" init-method="init"
+ class="org.apache.cxf.fediz.service.oidc.clients.ClientRegistrationService">
+ <property name="dataProvider" ref="oauthProvider"/>
+ <property name="clientProvider" ref="oauthProvider"/>
+ <!--
+ <property name="clientScopes" ref="supportedScopes"/>
+ -->
+ <property name="homeRealms">
+ <map>
+ <entry key="urn:org:apache:cxf:fediz:idp:realm-A" value="IDP of Realm A" />
+ <entry key="urn:org:apache:cxf:fediz:idp:realm-B" value="IDP of Realm B" />
+ </map>
+ </property>
+ </bean>
+
+ <!-- Console linking to the client registration service -->
+ <bean id="consoleService" class="org.apache.cxf.fediz.service.oidc.console.UserConsoleService">
+ <property name="clientRegService" ref="clientRegService"/>
+ </bean>
+ <jaxrs:server address="/console">
+ <jaxrs:serviceBeans>
+ <ref bean="consoleService"/>
+ </jaxrs:serviceBeans>
+ <jaxrs:providers>
+ <ref bean="viewProvider"/>
+ </jaxrs:providers>
+ </jaxrs:server>
+
+ <bean id="viewProvider" class="org.apache.cxf.jaxrs.provider.RequestDispatcherProvider">
+ <property name="useClassNames" value="true"/>
+ <property name="locationPrefix" value="/WEB-INF/views/"/>
+ <property name="beanName" value="data"/>
+ <property name="dispatcherName" value="jsp"/>
+ <property name="resourcePaths">
+ <map>
+ <entry key="/remove" value="/WEB-INF/views/registeredClients.jsp"/>
+ </map>
+ </property>
+ <property name="classResources">
+ <map>
+ <entry key="org.apache.cxf.fediz.service.oidc.clients.InvalidRegistration" value="/WEB-INF/views/invalidRegistration.jsp"/>
+ </map>
+ </property>
+ </bean>
+
+ <!-- AccessTokenService response filter which adds IdTokens to client responses -->
+ <bean id="idTokenFilter" class="org.apache.cxf.rs.security.oidc.idp.IdTokenResponseFilter">
+ <!--
+ <property name="signWithClientSecret" value="true"/>
+ -->
+ </bean>
+ <bean id="refreshTokenHandler" class="org.apache.cxf.rs.security.oauth2.grants.refresh.RefreshTokenGrantHandler">
+ <property name="dataProvider" ref="oauthProvider"/>
+ </bean>
+ <bean id="clientCredsHandler" class="org.apache.cxf.rs.security.oauth2.grants.clientcred.ClientCredentialsGrantHandler">
+ <property name="dataProvider" ref="oauthProvider"/>
+ </bean>
+ <!-- Access Token service -->
+ <bean id="accessTokenService" class="org.apache.cxf.rs.security.oauth2.services.AccessTokenService">
+ <property name="dataProvider" ref="oauthProvider"/>
+ <property name="responseFilter" ref="idTokenFilter"/>
+ <property name="grantHandler" ref="clientCredsHandler"/>
+ <property name="canSupportPublicClients" value="true"/>
+ </bean>
+ <!-- Access Token Introspection service -->
+ <bean id="accessTokenIntrospectionService" class="org.apache.cxf.rs.security.oauth2.services.TokenIntrospectionService">
+ <property name="dataProvider" ref="oauthProvider"/>
+ <property name="blockUnauthorizedRequests" value="false"/>
+ </bean>
+ <bean id="oauthJsonProvider" class="org.apache.cxf.rs.security.oauth2.provider.OAuthJSONProvider"/>
+ <jaxrs:server address="/oauth2">
+ <jaxrs:serviceBeans>
+ <ref bean="accessTokenService"/>
+ <ref bean="accessTokenIntrospectionService"/>
+ </jaxrs:serviceBeans>
+ <jaxrs:providers>
+ <ref bean="oauthJsonProvider"/>
+ </jaxrs:providers>
+ <jaxrs:properties>
+ <entry key="rs.security.signature.properties" value="rs.security.properties"/>
+ <entry key="rs.security.signature.key.password.provider" value-ref="keyPasswordProvider"/>
+ </jaxrs:properties>
+ </jaxrs:server>
+
+ <http:conduit name="*.http-conduit">
+ <http:tlsClientParameters
+ disableCNCheck="true">
+ <sec:trustManagers>
+ <sec:keyStore type="jks" password="tompass" resource="server.jks" />
+ </sec:trustManagers>
+ </http:tlsClientParameters>
+ </http:conduit>
+
+</beans>
+
diff --git a/systests/oidc/src/test/resources/oidc/data-manager.xml b/systests/oidc/src/test/resources/oidc/data-manager.xml
new file mode 100644
index 0000000..3397962
--- /dev/null
+++ b/systests/oidc/src/test/resources/oidc/data-manager.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/util
+ http://www.springframework.org/schema/util/spring-util.xsd
+ ">
+
+ <bean id="applicationContextProvider" class="org.apache.cxf.fediz.service.oidc.handler.hrd.ApplicationContextProvider"/>
+
+ <!-- List of accepted scopes -->
+ <util:map id="supportedScopes">
+ <entry key="openid" value="Access the authentication claims" />
+ <entry key="refreshToken" value="Refresh access tokens" />
+ </util:map>
+
+ <!--
+ List of required scopes that must be available in request URIs when
+ client redirects users to OIDC
+ -->
+ <util:list id="coreScopes">
+ <value>openid</value>
+ </util:list>
+
+ <!--
+ Typically the scopes authorized by the user will be reported back to the client,
+ reporting an approved refreshToken scope is currently disabled
+ -->
+ <util:list id="invisibleToClientScopes">
+ <value>refreshToken</value>
+ </util:list>
+
+ <!--
+ To support the alternative data persistence strategies: either register a custom
+ AbstractCodeDataProvider extension or implement AuthorizationCodeDataProvider directly
+ -->
+ <bean id="oauthProvider"
+ class="org.apache.cxf.fediz.service.oidc.OAuthDataProviderImpl"
+ init-method="init" destroy-method="close">
+ <!-- List of accepted scopes -->
+ <property name="supportedScopes" ref="supportedScopes"/>
+ <!--
+ List of scopes that the consent/authorization form should make
+ selected by default. For example, asking a user to do an extra click
+ to approve an "oidc" scope is a redundant operation because this scope
+ is required anyway.
+ -->
+ <property name="defaultScopes" ref="coreScopes"/>
+
+ <property name="invisibleToClientScopes" ref="invisibleToClientScopes"/>
+ <!--
+ <property name="accessTokenLifetime" value="3600"/>
+ -->
+ <!--
+ <property name="supportPreauthorizedTokens" value="true"/>
+ -->
+ <property name="contextName" value="sts"/>
+ </bean>
+
+ <!-- Custom SubjectCreator where IdToken is created -->
+ <bean id="subjectCreator" class="org.apache.cxf.fediz.service.oidc.FedizSubjectCreator">
+ <property name="idTokenIssuer" value="accounts.fediz.com"/>
+ </bean>
+
+</beans>
+
diff --git a/systests/oidc/src/test/resources/sts.jaas b/systests/oidc/src/test/resources/sts.jaas
new file mode 100644
index 0000000..fdb759e
--- /dev/null
+++ b/systests/oidc/src/test/resources/sts.jaas
@@ -0,0 +1,10 @@
+
+sts {
+ org.apache.cxf.ws.security.trust.STSLoginModule required
+ require.roles="true"
+ disable.on.behalf.of="true"
+ wsdl.location="https://localhost:${idp.https.port}/fediz-idp-sts/REALMA/STSServiceTransportUT?wsdl"
+ service.name="{http://docs.oasis-open.org/ws-sx/ws-trust/200512/}SecurityTokenService"
+ endpoint.name="{http://docs.oasis-open.org/ws-sx/ws-trust/200512/}TransportUT_Port";
+};
+