Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Master] Add Internal Token Revocation implementation for token persistence removal feature #12174

Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public void configure(Map<String, String> properties) {
public EventPublisher getEventPublisher(EventPublisherType eventPublisherType) {
switch (eventPublisherType) {
case TOKEN_REVOCATION:
case TOKEN_REVOKE_BY_CONSUMER_KEY_EVENT:
case TOKEN_REVOKE_BY_USER_EVENT:
case ASYNC_WEBHOOKS:
return new EventHubEventStreamServiceEventPublisher();
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,7 @@ public enum EventPublisherType {
GLOBAL_CACHE_INVALIDATION,
TOKEN_REVOCATION,
ASYNC_WEBHOOKS,
ORGANIZATION_PURGE
ORGANIZATION_PURGE,
TOKEN_REVOKE_BY_CONSUMER_KEY_EVENT,
TOKEN_REVOKE_BY_USER_EVENT,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 LLC. 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.wso2.carbon.apimgt.gateway.dto;

import com.google.gson.annotations.SerializedName;

import java.util.List;

public class RevokeConditionsDTO {

@SerializedName("revokedJWTList")
private List<RevokedJWTTokenDTO> revokedJWTList;

@SerializedName("revokedJWTConsumerKeyList")
private List<RevokedJWTConsumerKeyDTO> revokedConsumerKeyList;

@SerializedName("revokedJWTUserList")
private List<RevokedJWTUserDTO> revokedUserList;

public List<RevokedJWTTokenDTO> getRevokedJWTList() {
return revokedJWTList;
}

public void setRevokedJWTList(List<RevokedJWTTokenDTO> revokedJWTList) {
this.revokedJWTList = revokedJWTList;
}

public List<RevokedJWTConsumerKeyDTO> getRevokedConsumerKeyList() {
return revokedConsumerKeyList;
}

public void setRevokedConsumerKeyList(List<RevokedJWTConsumerKeyDTO> revokedConsumerKeyList) {
this.revokedConsumerKeyList = revokedConsumerKeyList;
}

public List<RevokedJWTUserDTO> getRevokedUserList() {
return revokedUserList;
}

public void setRevokedUserList(List<RevokedJWTUserDTO> revokedUserList) {
this.revokedUserList = revokedUserList;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

/*
* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 LLC. 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.wso2.carbon.apimgt.gateway.dto;

import com.google.gson.annotations.*;

/**
* DTO of revoked JWT Consumer key event
*/
public class RevokedJWTConsumerKeyDTO {

@SerializedName("consumer_key")
private String consumerKey;
@SerializedName("revocation_time")
private Long revocationTime;
@SerializedName("organization")
private String organization;

public void setRevocationTime(Long revocationTime) {
this.revocationTime = revocationTime;
}

public Long getRevocationTime() {
return revocationTime;
}

public String getConsumerKey() {
return consumerKey;
}

public void setConsumerKey(String consumerKey) {
this.consumerKey = consumerKey;
}

public String getOrganization() {
return organization;
}

public void setOrganization(String organization) {
this.organization = organization;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 LLC. 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.wso2.carbon.apimgt.gateway.dto;

import com.google.gson.annotations.SerializedName;

/**
* DTO of revoked JWT User event
*/
public class RevokedJWTUserDTO {

@SerializedName("subject_id")
private String subjectId;
@SerializedName("subject_id_type")
private String subjectIdType;
@SerializedName("revocation_time")
private Long revocationTime;
@SerializedName("organization")
private String organization;

public String getSubjectId() {
return subjectId;
}

public void setSubjectId(String subjectId) {
this.subjectId = subjectId;
}

public String getSubjectIdType() {
return subjectIdType;
}

public void setSubjectIdType(String subjectIdType) {
this.subjectIdType = subjectIdType;
}

public Long getRevocationTime() {
return revocationTime;
}

public void setRevocationTime(Long revocationTime) {
this.revocationTime = revocationTime;
}

public String getOrganization() {
return organization;
}

public void setOrganization(String organization) {
this.organization = organization;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
import org.wso2.carbon.apimgt.gateway.handlers.security.AuthenticationContext;
import org.wso2.carbon.apimgt.gateway.handlers.streaming.websocket.WebSocketApiConstants;
import org.wso2.carbon.apimgt.gateway.internal.ServiceReferenceHolder;
import org.wso2.carbon.apimgt.gateway.jwt.RevokedJWTDataHolder;
import org.wso2.carbon.apimgt.gateway.jwt.*;
Lakith-Rambukkanage marked this conversation as resolved.
Show resolved Hide resolved
import org.wso2.carbon.apimgt.gateway.utils.GatewayUtils;
import org.wso2.carbon.apimgt.impl.APIConstants;
import org.wso2.carbon.apimgt.impl.APIManagerConfiguration;
Expand All @@ -62,6 +62,7 @@
import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration;

import java.security.cert.Certificate;
import java.text.*;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
Expand Down Expand Up @@ -155,7 +156,13 @@ public AuthenticationContext authenticate(SignedJWTInfo signedJWTInfo, MessageCo
String matchingResource = (String) synCtx.getProperty(APIConstants.API_ELECTED_RESOURCE);
String jwtTokenIdentifier = getJWTTokenIdentifier(signedJWTInfo);
String jwtHeader = signedJWTInfo.getSignedJWT().getHeader().toString();

long jwtGeneratedTime = 0;
try {
jwtGeneratedTime = signedJWTInfo.getSignedJWT().getJWTClaimsSet().getIssueTime().getTime();
} catch (ParseException e) {
log.error("Error while obtaining JWT token generated time certificate. "
+ GatewayUtils.getMaskedToken(jwtHeader));
}
// Check for CNF validation
if (!isCNFValidationDisabled(disableCNFValidation, false)) {
try {
Expand All @@ -176,6 +183,42 @@ public AuthenticationContext authenticate(SignedJWTInfo signedJWTInfo, MessageCo
throw new APISecurityException(APISecurityConstants.API_AUTH_INVALID_CREDENTIALS,
"Invalid JWT token");
}
if (jwtGeneratedTime != 0 && InternalRevokedJWTDataHolder.getInstance()
.isJWTTokenClientIdExistsInRevokedMap((String) signedJWTInfo.getJwtClaimsSet()
.getClaim("client_id"), jwtGeneratedTime)) {
if (log.isDebugEnabled()) {
log.debug("Consumer key retrieved from the jwt token map is in revoked consumer key map." +
" Token: " + GatewayUtils.getMaskedToken(jwtHeader));
}
log.error("Invalid JWT token. " + GatewayUtils.getMaskedToken(jwtHeader));
throw new APISecurityException(APISecurityConstants.API_AUTH_INVALID_CREDENTIALS,
"Invalid JWT token");
}
if (jwtGeneratedTime != 0 && signedJWTInfo.getJwtClaimsSet().getSubject()
.equals(signedJWTInfo.getJwtClaimsSet().getClaim("client_id"))
Lakith-Rambukkanage marked this conversation as resolved.
Show resolved Hide resolved
&& InternalRevokedJWTDataHolder.getInstance().isJWTTokenClientIdExistsInRevokedAppOnlyMap(
signedJWTInfo.getJwtClaimsSet().getSubject(), jwtGeneratedTime)) {
// handle user event revocations of app tokens since the 'sub' claim is client id
if (log.isDebugEnabled()) {
log.debug("Consumer key retrieved from the jwt token map is in revoked consumer key map." +
" Token: " + GatewayUtils.getMaskedToken(jwtHeader));
}
log.error("Invalid JWT token. " + GatewayUtils.getMaskedToken(jwtHeader));
throw new APISecurityException(APISecurityConstants.API_AUTH_INVALID_CREDENTIALS,
"Invalid JWT token");
}
if (jwtGeneratedTime != 0 && !signedJWTInfo.getJwtClaimsSet().getSubject()
.equals(signedJWTInfo.getJwtClaimsSet().getClaim("client_id"))
&& InternalRevokedJWTDataHolder.getInstance().isJWTTokenUserIdExistsInRevokedMap(
signedJWTInfo.getJwtClaimsSet().getSubject(), jwtGeneratedTime)) {
if (log.isDebugEnabled()) {
log.debug("User id retrieved from the jwt token map is in revoked user id map." +
" Token: " + GatewayUtils.getMaskedToken(jwtHeader));
}
log.error("Invalid JWT token. " + GatewayUtils.getMaskedToken(jwtHeader));
throw new APISecurityException(APISecurityConstants.API_AUTH_INVALID_CREDENTIALS,
"Invalid JWT token");
}
}

JWTValidationInfo jwtValidationInfo = getJwtValidationInfo(signedJWTInfo, jwtTokenIdentifier);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
Lakith-Rambukkanage marked this conversation as resolved.
Show resolved Hide resolved
*
* WSO2 Inc. 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.wso2.carbon.apimgt.gateway.jwt;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.sql.Timestamp;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* Singleton which stores the rule maps for revoked JWTs
*/
public class InternalRevokedJWTDataHolder {
private static final Log log = LogFactory.getLog(InternalRevokedJWTDataHolder.class);
private static final Map<String, Long> internalRevokedConsumerKeyMap = new ConcurrentHashMap<>();
private static final Map<String, Long> internalRevokedConsumerKeyAppOnlyMap = new ConcurrentHashMap<>();
// User UUID (jwt claim) -> revoked timestamp
private static final Map<String, Long> internalRevokedUserEventRuleMap = new ConcurrentHashMap<>();
private static final InternalRevokedJWTDataHolder instance = new InternalRevokedJWTDataHolder();

private InternalRevokedJWTDataHolder() {

}

/**
* This method can be used to get the singleton instance of this class.
*
* @return the singleton instance.
*/
public static InternalRevokedJWTDataHolder getInstance() {
return instance;
}


public void addInternalRevokedJWTClientIDToMap(String consumerKey, Long revocationTime) {

if (log.isDebugEnabled()) {
log.debug("Adding internal revoked JWT client Id, revocation time pair to the " +
"revoked map :" + consumerKey + " , revocationTime:" + revocationTime);
}
internalRevokedConsumerKeyMap.put(consumerKey, revocationTime);
}

public boolean isJWTTokenClientIdExistsInRevokedMap(String consumerKey, Long jwtGeneratedTimestamp) {

Long jwtRevokedTime = internalRevokedConsumerKeyMap.get(consumerKey);

if (jwtRevokedTime != null) {
Timestamp jwtRevokedTimestamp = new Timestamp(jwtRevokedTime);
jwtRevokedTimestamp.toLocalDateTime();
return jwtRevokedTimestamp.after(new Timestamp(jwtGeneratedTimestamp));
}
return false;
}

public void addInternalRevokedJWTClientIDToAppOnlyMap(String consumerKey, Long revocationTime) {

if (log.isDebugEnabled()) {
log.debug("Adding internal revoked JWT client Id, revocation time pair to the " +
"revoked app only map :" + consumerKey + " , revocationTime:" + revocationTime);
}
internalRevokedConsumerKeyAppOnlyMap.put(consumerKey, revocationTime);
}

public boolean isJWTTokenClientIdExistsInRevokedAppOnlyMap(String consumerKey, Long jwtGeneratedTimestamp) {

Long jwtRevokedTime = internalRevokedConsumerKeyAppOnlyMap.get(consumerKey);

if (jwtRevokedTime != null) {
Timestamp jwtRevokedTimestamp = new Timestamp(jwtRevokedTime);
return jwtRevokedTimestamp.after(new Timestamp(jwtGeneratedTimestamp));
}

return false;
}

public void addInternalRevokedJWTUserIDToMap(String userUUID, Long revocationTime) {

if (log.isDebugEnabled()) {
log.debug("Adding internal revoked JWT user id, revocation time value pair to the " +
"revoked map :" + userUUID + " , revocationTime: " + revocationTime);
}
internalRevokedUserEventRuleMap.put(userUUID, revocationTime);
}

public boolean isJWTTokenUserIdExistsInRevokedMap(String user, Long jwtGeneratedTimestamp) {

Long jwtRevokedTime = internalRevokedUserEventRuleMap.get(user);

if (jwtRevokedTime != null) {
Timestamp jwtRevokedTimestamp = new Timestamp(jwtRevokedTime);
return jwtRevokedTimestamp.after(new Timestamp(jwtGeneratedTimestamp));
}
return false;
}
}
Loading
Loading