Skip to content
This repository was archived by the owner on Jan 20, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.networknt.oauth.cache.model.Client;
import com.networknt.oauth.cache.model.RefreshToken;
import com.networknt.oauth.cache.model.User;
import com.networknt.oauth.token.helper.HttpAuth;
import com.networknt.security.JwtHelper;
import com.networknt.status.Status;
import com.networknt.utility.HashUtil;
Expand All @@ -27,8 +28,6 @@
import java.security.spec.InvalidKeySpecException;
import java.util.*;

import static java.nio.charset.StandardCharsets.UTF_8;

public class Oauth2TokenPostHandler implements HttpHandler {
private static final Logger logger = LoggerFactory.getLogger(Oauth2TokenPostHandler.class);

Expand Down Expand Up @@ -77,13 +76,13 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
}
try {
if("client_credentials".equals(formMap.get("grant_type"))) {
exchange.getResponseSender().send(mapper.writeValueAsString(handleClientCredentials(exchange, (String)formMap.get("scope"))));
exchange.getResponseSender().send(mapper.writeValueAsString(handleClientCredentials(exchange, (String)formMap.get("scope"), formMap)));
} else if("authorization_code".equals(formMap.get("grant_type"))) {
exchange.getResponseSender().send(mapper.writeValueAsString(handleAuthorizationCode(exchange, (String)formMap.get("code"), (String)formMap.get("redirect_uri"))));
exchange.getResponseSender().send(mapper.writeValueAsString(handleAuthorizationCode(exchange, (String)formMap.get("code"), (String)formMap.get("redirect_uri"), formMap)));
} else if("password".equals(formMap.get("grant_type"))) {
exchange.getResponseSender().send(mapper.writeValueAsString(handlePassword(exchange, (String)formMap.get("username"), (String)formMap.get("password"), (String)formMap.get("scope"))));
exchange.getResponseSender().send(mapper.writeValueAsString(handlePassword(exchange, (String)formMap.get("username"), (String)formMap.get("password"), (String)formMap.get("scope"), formMap)));
} else if("refresh_token".equals(formMap.get("grant_type"))) {
exchange.getResponseSender().send(mapper.writeValueAsString(handleRefreshToken(exchange, (String)formMap.get("refresh_token"), (String)formMap.get("scope"))));
exchange.getResponseSender().send(mapper.writeValueAsString(handleRefreshToken(exchange, (String)formMap.get("refresh_token"), (String)formMap.get("scope"), formMap)));
} else {
Status status = new Status(UNSUPPORTED_GRANT_TYPE, formMap.get("grant_type"));
exchange.setStatusCode(status.getStatusCode());
Expand All @@ -100,9 +99,9 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
}

@SuppressWarnings("unchecked")
private Map<String, Object> handleClientCredentials(HttpServerExchange exchange, String scope) throws ApiException {
private Map<String, Object> handleClientCredentials(HttpServerExchange exchange, String scope, Map<String, Object> formMap) throws ApiException {
if(logger.isDebugEnabled()) logger.debug("scope = " + scope);
Client client = authenticateClient(exchange);
Client client = authenticateClient(exchange, formMap);
if(client != null) {
if(scope == null) {
scope = client.getScope();
Expand All @@ -129,9 +128,9 @@ private Map<String, Object> handleClientCredentials(HttpServerExchange exchange,
}

@SuppressWarnings("unchecked")
private Map<String, Object> handleAuthorizationCode(HttpServerExchange exchange, String code, String redirectUri) throws ApiException {
private Map<String, Object> handleAuthorizationCode(HttpServerExchange exchange, String code, String redirectUri, Map<String, Object> formMap) throws ApiException {
if(logger.isDebugEnabled()) logger.debug("code = " + code + " redirectUri = " + redirectUri);
Client client = authenticateClient(exchange);
Client client = authenticateClient(exchange, formMap);
if(client != null) {
Map<String, String> codeMap = (Map<String, String>)CacheStartupHookProvider.hz.getMap("codes").remove(code);
String userId = codeMap.get("userId");
Expand Down Expand Up @@ -187,9 +186,9 @@ private Map<String, Object> handleAuthorizationCode(HttpServerExchange exchange,
}

@SuppressWarnings("unchecked")
private Map<String, Object> handlePassword(HttpServerExchange exchange, String userId, String password, String scope) throws ApiException {
private Map<String, Object> handlePassword(HttpServerExchange exchange, String userId, String password, String scope, Map<String, Object> formMap) throws ApiException {
if(logger.isDebugEnabled()) logger.debug("userId = " + userId + " scope = " + scope);
Client client = authenticateClient(exchange);
Client client = authenticateClient(exchange, formMap);
if(client != null) {
// authenticate user with credentials
if(userId != null) {
Expand Down Expand Up @@ -248,9 +247,9 @@ private Map<String, Object> handlePassword(HttpServerExchange exchange, String u


@SuppressWarnings("unchecked")
private Map<String, Object> handleRefreshToken(HttpServerExchange exchange, String refreshToken, String scope) throws ApiException {
private Map<String, Object> handleRefreshToken(HttpServerExchange exchange, String refreshToken, String scope, Map<String, Object> formMap) throws ApiException {
if(logger.isDebugEnabled()) logger.debug("refreshToken = " + refreshToken + " scope = " + scope);
Client client = authenticateClient(exchange);
Client client = authenticateClient(exchange, formMap);
if(client != null) {
// make sure that the refresh token can be found and client_id matches.
IMap<String, RefreshToken> tokens = CacheStartupHookProvider.hz.getMap("tokens");
Expand Down Expand Up @@ -303,45 +302,48 @@ private Map<String, Object> handleRefreshToken(HttpServerExchange exchange, Stri
return new HashMap<>(); // return an empty hash map. this is actually not reachable at all.
}

private Client authenticateClient(HttpServerExchange exchange) throws ApiException {
String auth = exchange.getRequestHeaders().getFirst("Authorization");
if(auth == null || auth.trim().length() == 0) {
throw new ApiException(new Status(MISSING_AUTHORIZATION_HEADER));

private Client authenticateClient(HttpServerExchange exchange, Map<String, Object> formMap) throws ApiException {
HttpAuth httpAuth = new HttpAuth(exchange);

String clientId;
String clientSecret;
if(!httpAuth.isHeaderAvailable()) {
clientId = (String)formMap.get("client_id");
clientSecret = (String)formMap.get("client_secret");
} else {
String basic = auth.substring(0, 5);
if("BASIC".equalsIgnoreCase(basic)) {
String credentials = auth.substring(6);
String clientId;
String clientSecret;
int pos = credentials.indexOf(':');
if(pos == -1) {
credentials = decodeCredentials(credentials);
}
pos = credentials.indexOf(':');
if(pos != -1) {
clientId = credentials.substring(0, pos);
clientSecret = credentials.substring(pos + 1);
IMap<String, Client> clients = CacheStartupHookProvider.hz.getMap("clients");
Client client = clients.get(clientId);
if(client == null) {
throw new ApiException(new Status(CLIENT_NOT_FOUND, clientId));
} else {
try {
if(HashUtil.validatePassword(clientSecret, client.getClientSecret())) {
return client;
} else {
throw new ApiException(new Status(UNAUTHORIZED_CLIENT));
}
} catch ( NoSuchAlgorithmException | InvalidKeySpecException e) {
logger.error("Exception:", e);
throw new ApiException(new Status(RUNTIME_EXCEPTION));
}
}
clientId = httpAuth.getClientId();
clientSecret = httpAuth.getClientSecret();
}

if(clientId == null || clientId.trim().isEmpty() || clientSecret == null || clientSecret.trim().isEmpty()) {
if(!httpAuth.isHeaderAvailable()) {
throw new ApiException(new Status(MISSING_AUTHORIZATION_HEADER));
} else if(httpAuth.isInvalidCredentials()) {
throw new ApiException(new Status(INVALID_BASIC_CREDENTIALS, httpAuth.getCredentials()));
} else {
throw new ApiException(new Status(INVALID_AUTHORIZATION_HEADER, httpAuth.getAuth()));
}
}

return validateClientSecret(clientId, clientSecret);
}

private Client validateClientSecret(String clientId, String clientSecret) throws ApiException {
IMap<String, Client> clients = CacheStartupHookProvider.hz.getMap("clients");
Client client = clients.get(clientId);
if(client == null) {
throw new ApiException(new Status(CLIENT_NOT_FOUND, clientId));
} else {
try {
if(HashUtil.validatePassword(clientSecret, client.getClientSecret())) {
return client;
} else {
throw new ApiException(new Status(INVALID_BASIC_CREDENTIALS, credentials));
throw new ApiException(new Status(UNAUTHORIZED_CLIENT));
}
} else {
throw new ApiException(new Status(INVALID_AUTHORIZATION_HEADER, auth));
} catch ( NoSuchAlgorithmException | InvalidKeySpecException e) {
logger.error("Exception:", e);
throw new ApiException(new Status(RUNTIME_EXCEPTION));
}
}
}
Expand All @@ -364,9 +366,7 @@ private JwtClaims mockAcClaims(String clientId, String scopeString, String userI
return claims;
}

private static String decodeCredentials(String cred) {
return new String(org.apache.commons.codec.binary.Base64.decodeBase64(cred), UTF_8);
}


private static boolean matchScope(String s1, String s2) {
boolean matched = true;
Expand Down
82 changes: 82 additions & 0 deletions token/src/main/java/com/networknt/oauth/token/helper/HttpAuth.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.networknt.oauth.token.helper;

import io.undertow.server.HttpServerExchange;

import static java.nio.charset.StandardCharsets.UTF_8;

public class HttpAuth {
private boolean headerAvailable;
private boolean basicAuth = false;
private boolean invalidCredentials;
private String clientId;
private String clientSecret;
private String credentials;
private String auth;

public HttpAuth(HttpServerExchange exchange) {
process(exchange);
}

private void process(HttpServerExchange exchange) {
auth = exchange.getRequestHeaders().getFirst("Authorization");
if(auth == null || auth.trim().length() == 0) {
headerAvailable = false;
} else {
headerAvailable = true;

String basic = auth.substring(0, 5);
if("BASIC".equalsIgnoreCase(basic)) {
basicAuth = true;
credentials = auth.substring(6);
int pos = credentials.indexOf(':');
if (pos == -1) {
credentials = decodeCredentials(credentials);
}
pos = credentials.indexOf(':');
if (pos != -1) {
clientId = credentials.substring(0, pos);
clientSecret = credentials.substring(pos + 1);
invalidCredentials = false;
} else {
invalidCredentials = true;
}
}
}
}

private String decodeCredentials(String cred) {
return new String(org.apache.commons.codec.binary.Base64.decodeBase64(cred), UTF_8);
}

public boolean isHeaderAvailable() {
return headerAvailable;
}

public String getClientId() {
return clientId;
}

public String getClientSecret() {
return clientSecret;
}

public boolean isInvalidCredentials() {
return invalidCredentials;
}

public boolean isValid() {
return isHeaderAvailable() && !isInvalidCredentials();
}

public boolean isBasicAuth() {
return basicAuth;
}

public String getCredentials() {
return credentials;
}

public String getAuth() {
return auth;
}
}
14 changes: 13 additions & 1 deletion token/src/main/resources/config/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"description": "encoded client_id and client_secret pair",
"in": "header",
"type": "string",
"required": true
"required": false
},
{
"name": "grant_type",
Expand All @@ -48,6 +48,18 @@
"required": true,
"in": "formData"
},
{
"name": "client_id",
"description": "used as alternative to authentication header for client authentication",
"type": "string",
"in": "formData"
},
{
"name": "client_secret",
"description": "used as alternative to authentication header for client authentication",
"type": "string",
"in": "formData"
},
{
"name": "code",
"description": "used in authorization_code to specify the code",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.networknt.oauth.token.handler;

import com.fasterxml.jackson.core.type.TypeReference;
import com.hazelcast.core.IMap;
import com.networknt.config.Config;
import com.networknt.oauth.cache.CacheStartupHookProvider;
import com.networknt.oauth.cache.model.RefreshToken;
Expand Down Expand Up @@ -70,6 +69,24 @@ public void testClientCredentialsToken() throws Exception {
Assert.assertTrue(body.indexOf("access_token") > 0);
}

@Test
public void testClientCredentialsTokenByFormData() throws Exception {
String url = "http://localhost:6882/oauth2/token";
CloseableHttpClient client = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);

List<NameValuePair> urlParameters = new ArrayList<>();
urlParameters.add(new BasicNameValuePair("grant_type", "client_credentials"));
urlParameters.add(new BasicNameValuePair("client_id", "f7d42348-c647-4efb-a52d-4c5787421e72"));
urlParameters.add(new BasicNameValuePair("client_secret", "f6h1FTI8Q3-7UScPZDzfXA"));
httpPost.setEntity(new UrlEncodedFormEntity(urlParameters));
HttpResponse response = client.execute(httpPost);
Assert.assertEquals(200, response.getStatusLine().getStatusCode());
String body = EntityUtils.toString(response.getEntity());
logger.debug("response body = " + body);
Assert.assertTrue(body.indexOf("access_token") > 0);
}

@Test
public void testTokenInvalidForm() throws Exception {
String url = "http://localhost:6882/oauth2/token";
Expand Down Expand Up @@ -110,11 +127,10 @@ public void testTokenMissingAuthHeader() throws Exception {
urlParameters.add(new BasicNameValuePair("grant_type", "client_credentials"));
httpPost.setEntity(new UrlEncodedFormEntity(urlParameters));
HttpResponse response = client.execute(httpPost);
//String body = EntityUtils.toString(response.getEntity());
Assert.assertEquals(400, response.getStatusLine().getStatusCode());
Assert.assertEquals(401, response.getStatusLine().getStatusCode());
Status status = Config.getInstance().getMapper().readValue(response.getEntity().getContent(), Status.class);
Assert.assertNotNull(status);
Assert.assertEquals("ERR11017", status.getCode());
Assert.assertEquals("ERR12002", status.getCode());
}

@Test
Expand Down