You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by lm...@apache.org on 2024/03/12 22:13:21 UTC
(knox) branch master updated: KNOX-3016 - add support for client credentials flow (#876)
This is an automated email from the ASF dual-hosted git repository.
lmccay pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new 8f38723bb KNOX-3016 - add support for client credentials flow (#876)
8f38723bb is described below
commit 8f38723bb6b8111eb93b01697e89dd98fb6f59f2
Author: lmccay <lm...@apache.org>
AuthorDate: Tue Mar 12 18:12:59 2024 -0400
KNOX-3016 - add support for client credentials flow (#876)
* KNOX-3016 - add support for client credentials flow
---
.../federation/jwt/filter/AbstractJWTFilter.java | 19 +++++++-
.../federation/jwt/filter/JWTFederationFilter.java | 41 ++++++++++++++--
...lientIdAndClientSecretFederationFilterTest.java | 54 ++++++++++++++++++++++
3 files changed, 109 insertions(+), 5 deletions(-)
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
index 07ce39030..90fd117b9 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
@@ -96,6 +96,8 @@ public abstract class AbstractJWTFilter implements Filter {
*/
public static final String JWT_EXPECTED_SIGALG = "jwt.expected.sigalg";
public static final String JWT_DEFAULT_SIGALG = "RS256";
+ public static final String TYPE = "type";
+ public static final String CLIENT_ID = "CLIENT_ID";
static JWTMessages log = MessagesFactory.get( JWTMessages.class );
@@ -300,8 +302,23 @@ public abstract class AbstractJWTFilter implements Filter {
public Subject createSubjectFromTokenIdentifier(final String tokenId) throws UnknownTokenException {
TokenMetadata metadata = tokenStateService.getTokenMetadata(tokenId);
+ String username = null;
if (metadata != null) {
- return createSubjectFromTokenData(metadata.getUserName(), null);
+ String type = metadata.getMetadata(TYPE);
+ // using tokenID and passcode as CLIENT_ID and CLIENT_SECRET will
+ // result in a metadata item called "type". If the value is set
+ // to CLIENT_ID then it will be assumed to be a CLIENT_ID and we
+ // will use the token id as the username. Since we don't know the
+ // token id until it is created, the username is always the same
+ // in the record. Using the token id makes it a unique username for
+ // audit and the like.
+ if (CLIENT_ID.equalsIgnoreCase(type)) {
+ username = tokenId;
+ }
+ else {
+ username = metadata.getUserName();
+ }
+ return createSubjectFromTokenData(username, null);
}
return null;
}
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
index 64ac556d0..9c2001057 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
@@ -58,6 +58,9 @@ public class JWTFederationFilter extends AbstractJWTFilter {
private static final JWTMessages LOGGER = MessagesFactory.get( JWTMessages.class );
/* A semicolon separated list of paths that need to bypass authentication */
public static final String JWT_UNAUTHENTICATED_PATHS_PARAM = "jwt.unauthenticated.path.list";
+ public static final String GRANT_TYPE = "grant_type";
+ public static final String CLIENT_CREDENTIALS = "client_credentials";
+ public static final String CLIENT_SECRET = "client_secret";
public enum TokenType {
JWT, Passcode;
@@ -239,15 +242,45 @@ public class JWTFederationFilter extends AbstractJWTFilter {
}
if (parsed == null) {
- token = request.getParameter(this.paramName);
- if (token != null) {
- parsed = Pair.of(TokenType.JWT, token);
- }
+ parsed = parseFromClientCredentialsFlow(request);
+ }
+
+ if (parsed == null) {
+ token = request.getParameter(this.paramName);
+ if (token != null) {
+ parsed = Pair.of(TokenType.JWT, token);
+ }
}
return parsed;
}
+ private Pair<TokenType, String> parseFromClientCredentialsFlow(ServletRequest request) {
+ Pair<TokenType, String> parsed = null;
+ String token = null;
+
+ /*
+ POST /{tenant}/oauth2/v2.0/token HTTP/1.1
+ Host: login.microsoftonline.com:443
+ Content-Type: application/x-www-form-urlencoded
+
+ client_id=535fb089-9ff3-47b6-9bfb-4f1264799865
+ &scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
+ &client_secret=sampleCredentials
+ &grant_type=client_credentials
+ */
+
+ String grantType = request.getParameter(GRANT_TYPE);
+ if (CLIENT_CREDENTIALS.equals(grantType)) {
+ // this is indeed a client credentials flow client_id and
+ // client_secret are expected now the client_id will be in
+ // the token as the token_id so we will get that later
+ token = request.getParameter(CLIENT_SECRET);
+ parsed = Pair.of(TokenType.Passcode, token);
+ }
+ return parsed;
+ }
+
private Pair<TokenType, String> parseFromHTTPBasicCredentials(final String header) {
Pair<TokenType, String> parsed = null;
final String base64Credentials = header.substring(BASIC.length()).trim();
diff --git a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/ClientIdAndClientSecretFederationFilterTest.java b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/ClientIdAndClientSecretFederationFilterTest.java
new file mode 100644
index 000000000..806125ad1
--- /dev/null
+++ b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/ClientIdAndClientSecretFederationFilterTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.provider.federation;
+
+
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+public class ClientIdAndClientSecretFederationFilterTest extends TokenIDAsHTTPBasicCredsFederationFilterTest {
+ @Override
+ protected void setTokenOnRequest(HttpServletRequest request, String authUsername, String authPassword) {
+ EasyMock.expect((Object)request.getHeader("Authorization")).andReturn("");
+ EasyMock.expect((Object)request.getParameter("grant_type")).andReturn("client_credentials");
+ EasyMock.expect((Object)request.getParameter("client_id")).andReturn(authUsername);
+ EasyMock.expect((Object)request.getParameter("client_secret")).andReturn(authPassword);
+ }
+
+ @Override
+ @Test
+ public void testInvalidUsername() throws Exception {
+ // there is no way to specify an invalid username for
+ // client credentials flow or at least no meaningful way
+ // to do so for our implementation. The client id is
+ // actually encoded in the client secret and that is used
+ // for the actual authentication with passcodes.
+ }
+
+ @Override
+ @Test
+ public void testInvalidJWTForPasscode() throws Exception {
+ // there is no way to specify an invalid username for
+ // client credentials flow or at least no meaningful way
+ // to do so for our implementation. The username is actually
+ // set by the JWTProvider when determining that the request
+ // is a client credentials flow.
+ }
+}