Skip to content
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
12 changes: 12 additions & 0 deletions openapi-security/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@
<groupId>com.networknt</groupId>
<artifactId>status</artifactId>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>basic-auth</artifactId>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>api-key</artifactId>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>server</artifactId>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-overlay</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,19 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
logger.debug("JwtVerifyHandler.handleRequest ends.");
return;
}
// only UnifiedSecurityHandler will have the jwkServiceIds as the third parameter.
if(handleJwt(exchange, null, reqPath, null)) {
if(logger.isDebugEnabled()) logger.debug("JwtVerifyHandler.handleRequest ends.");
Handler.next(exchange, next);
}
}

public boolean handleJwt(HttpServerExchange exchange, String pathPrefix, String reqPath, List<String> jwkServiceIds) throws Exception {
Map<String, Object> auditInfo = null;
HeaderMap headerMap = exchange.getRequestHeaders();
String authorization = headerMap.getFirst(Headers.AUTHORIZATION);

if (logger.isTraceEnabled() && authorization != null)
if (logger.isTraceEnabled() && authorization != null && authorization.length() > 10)
logger.trace("Authorization header = " + authorization.substring(0, 10));

authorization = this.getScopeToken(authorization, headerMap);
Expand All @@ -125,7 +133,7 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {

try {

JwtClaims claims = jwtVerifier.verifyJwt(jwt, ignoreExpiry, true, reqPath);
JwtClaims claims = jwtVerifier.verifyJwt(jwt, ignoreExpiry, true, pathPrefix, reqPath, jwkServiceIds);

if (logger.isTraceEnabled())
logger.trace("claims = " + claims.toJson());
Expand Down Expand Up @@ -156,7 +164,7 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
if (!config.isEnableH2c() && this.checkForH2CRequest(headerMap)) {
setExchangeStatus(exchange, STATUS_METHOD_NOT_ALLOWED);
if (logger.isDebugEnabled()) logger.debug("JwtVerifyHandler.handleRequest ends with an error.");
return;
return false;
}

String callerId = headerMap.getFirst(HttpStringConstants.CALLER_ID);
Expand All @@ -178,19 +186,19 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
} else {
// this will return an error message to the client.
}
return;
return false;
}

/* validate scope from operation */
String scopeHeader = headerMap.getFirst(HttpStringConstants.SCOPE_TOKEN);
String scopeJwt = JwtVerifier.getJwtFromAuthorization(scopeHeader);
List<String> secondaryScopes = new ArrayList<>();

if(!this.hasValidSecondaryScopes(exchange, scopeJwt, secondaryScopes, ignoreExpiry, reqPath, auditInfo)) {
return;
if(!this.hasValidSecondaryScopes(exchange, scopeJwt, secondaryScopes, ignoreExpiry, pathPrefix, reqPath, jwkServiceIds, auditInfo)) {
return false;
}
if(!this.hasValidScope(exchange, scopeHeader, secondaryScopes, claims, operation)) {
return;
return false;
}
}
if (logger.isTraceEnabled())
Expand All @@ -199,7 +207,7 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
if (logger.isDebugEnabled())
logger.debug("JwtVerifyHandler.handleRequest ends.");

Handler.next(exchange, next);
return true;
} catch (InvalidJwtException e) {

// only log it and unauthorized is returned.
Expand Down Expand Up @@ -227,8 +235,8 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
setExchangeStatus(exchange, STATUS_MISSING_AUTH_TOKEN);
exchange.endExchange();
}
return true;
}

/**
* Get authToken from X-Scope-Token header.
* This covers situations where there is a secondary auth token.
Expand Down Expand Up @@ -319,13 +327,13 @@ protected Operation getOperation(HttpServerExchange exchange, OpenApiOperation o
* @param reqPath - the request path as string
* @return - return true if the secondary scopes are valid or if there are no secondary scopes.
*/
protected boolean hasValidSecondaryScopes(HttpServerExchange exchange, String scopeJwt, List<String> secondaryScopes, boolean ignoreExpiry, String reqPath, Map<String, Object> auditInfo) {
protected boolean hasValidSecondaryScopes(HttpServerExchange exchange, String scopeJwt, List<String> secondaryScopes, boolean ignoreExpiry, String pathPrefix, String reqPath, List<String> jwkServiceIds, Map<String, Object> auditInfo) {
if (scopeJwt != null) {
if (logger.isTraceEnabled())
logger.trace("start verifying scope token = " + scopeJwt.substring(0, 10));

try {
JwtClaims scopeClaims = jwtVerifier.verifyJwt(scopeJwt, ignoreExpiry, true, reqPath);
JwtClaims scopeClaims = jwtVerifier.verifyJwt(scopeJwt, ignoreExpiry, true, pathPrefix, reqPath, jwkServiceIds);
Object scopeClaim = scopeClaims.getClaimValue(Constants.SCOPE_STRING);

if (scopeClaim instanceof String) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.networknt.openapi;

import java.util.List;

public class UnifiedPathPrefixAuth {
String pathPrefix;
boolean basic;
boolean jwt;
boolean apikey;
List<String> jwkServiceIds;

public String getPathPrefix() {
return pathPrefix;
}

public void setPathPrefix(String pathPrefix) {
this.pathPrefix = pathPrefix;
}

public boolean isBasic() {
return basic;
}

public void setBasic(boolean basic) {
this.basic = basic;
}

public boolean isJwt() {
return jwt;
}

public void setJwt(boolean jwt) {
this.jwt = jwt;
}

public boolean isApikey() {
return apikey;
}

public void setApikey(boolean apikey) {
this.apikey = apikey;
}

public List<String> getJwkServiceIds() {
return jwkServiceIds;
}

public void setJwkServiceIds(List<String> jwkServiceIds) {
this.jwkServiceIds = jwkServiceIds;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package com.networknt.openapi;

import com.fasterxml.jackson.core.type.TypeReference;
import com.networknt.config.Config;
import com.networknt.config.ConfigException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class UnifiedSecurityConfig {
private static final Logger logger = LoggerFactory.getLogger(UnifiedSecurityConfig.class);
public static final String CONFIG_NAME = "unified-security";
public static final String ENABLED = "enabled";
public static final String ANONYMOUS_PREFIXES = "anonymousPrefixes";
public static final String PATH_PREFIX_AUTHS = "pathPrefixAuths";
public static final String PREFIX = "prefix";
public static final String BASIC = "basic";
public static final String JWT = "jwt";
public static final String APIKEY = "apikey";
public static final String JWK_SERVICE_IDS = "jwkServiceIds";

boolean enabled;
List<String> anonymousPrefixes;
List<UnifiedPathPrefixAuth> pathPrefixAuths;

private Config config;
private Map<String, Object> mappedConfig;

private UnifiedSecurityConfig() {
this(CONFIG_NAME);
}

/**
* Please note that this constructor is only for testing to load different config files
* to test different configurations.
* @param configName String
*/
private UnifiedSecurityConfig(String configName) {
config = Config.getInstance();
mappedConfig = config.getJsonMapConfigNoCache(configName);
setConfigData();
setConfigList();
}
public static UnifiedSecurityConfig load() {
return new UnifiedSecurityConfig();
}

public static UnifiedSecurityConfig load(String configName) {
return new UnifiedSecurityConfig(configName);
}

void reload() {
mappedConfig = config.getJsonMapConfigNoCache(CONFIG_NAME);
setConfigData();
setConfigList();
}

public boolean isEnabled() {
return enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public List<String> getAnonymousPrefixes() {
return anonymousPrefixes;
}

public void setAnonymousPrefixes(List<String> anonymousPrefixes) {
this.anonymousPrefixes = anonymousPrefixes;
}

public List<UnifiedPathPrefixAuth> getPathPrefixAuths() {
return pathPrefixAuths;
}

public void setPathPrefixAuths(List<UnifiedPathPrefixAuth> pathPrefixAuths) {
this.pathPrefixAuths = pathPrefixAuths;
}

private void setConfigData() {
Object object = mappedConfig.get(ENABLED);
if(object != null && (Boolean) object) {
setEnabled(true);
}
}

private void setConfigList() {
// anonymous prefixes
if (mappedConfig != null && mappedConfig.get(ANONYMOUS_PREFIXES) != null) {
Object object = mappedConfig.get(ANONYMOUS_PREFIXES);
anonymousPrefixes = new ArrayList<>();
if(object instanceof String) {
String s = (String)object;
s = s.trim();
if(logger.isTraceEnabled()) logger.trace("s = " + s);
if(s.startsWith("[")) {
// json format
try {
anonymousPrefixes = Config.getInstance().getMapper().readValue(s, new TypeReference<List<String>>() {});
} catch (Exception e) {
throw new ConfigException("could not parse the anonymousPrefixes json with a list of strings.");
}
} else {
// comma separated
anonymousPrefixes = Arrays.asList(s.split("\\s*,\\s*"));
}
} else if (object instanceof List) {
List prefixes = (List)object;
prefixes.forEach(item -> {
anonymousPrefixes.add((String)item);
});
} else {
throw new ConfigException("anonymousPrefixes must be a string or a list of strings.");
}
}

// path prefix auth mapping
if (mappedConfig.get(PATH_PREFIX_AUTHS) != null) {
Object object = mappedConfig.get(PATH_PREFIX_AUTHS);
pathPrefixAuths = new ArrayList<>();
if(object instanceof String) {
String s = (String)object;
s = s.trim();
if(logger.isTraceEnabled()) logger.trace("pathPrefixAuth s = " + s);
if(s.startsWith("[")) {
// json format
try {
pathPrefixAuths = Config.getInstance().getMapper().readValue(s, new TypeReference<List<UnifiedPathPrefixAuth>>() {});
} catch (Exception e) {
throw new ConfigException("could not parse the pathPrefixAuths json with a list of string and object.");
}
} else {
throw new ConfigException("pathPrefixAuths must be a list of string object map.");
}
} else if (object instanceof List) {
// the object is a list of map, we need convert it to PathPrefixAuth object.
List<Map<String, Object>> values = (List<Map<String, Object>>)object;
for(Map<String, Object> value: values) {
UnifiedPathPrefixAuth unifiedPathPrefixAuth = new UnifiedPathPrefixAuth();

unifiedPathPrefixAuth.setPathPrefix((String)value.get(PREFIX));
unifiedPathPrefixAuth.setBasic(value.get(BASIC) == null ? false : (Boolean)value.get(BASIC));
unifiedPathPrefixAuth.setJwt(value.get(JWT) == null ? false : (Boolean)value.get(JWT));
unifiedPathPrefixAuth.setApikey(value.get(APIKEY) == null ? false : (Boolean)value.get(APIKEY));
Object ids = value.get(JWK_SERVICE_IDS);
if(ids instanceof String) {
String s = (String)value.get(JWK_SERVICE_IDS);
if(s.startsWith("[")) {
// json format
try {
unifiedPathPrefixAuth.setJwkServiceIds(Config.getInstance().getMapper().readValue(s, new TypeReference<List<String>>() {}));
} catch (Exception e) {
throw new ConfigException("could not parse the jwkServiceIds json with a list of strings.");
}
} else {
// comma separated
unifiedPathPrefixAuth.setJwkServiceIds(Arrays.asList(s.split("\\s*,\\s*")));
}
} else if(ids instanceof List ) {
// it must be a json array.
unifiedPathPrefixAuth.setJwkServiceIds((List)ids);
}
pathPrefixAuths.add(unifiedPathPrefixAuth);
}
} else {
throw new ConfigException("pathPrefixAuth must be a list of string object map.");
}
}
}

}
Loading