Skip to content

Commit

Permalink
Merge pull request #12263 from dushaniw/4.3.0-token-persistence
Browse files Browse the repository at this point in the history
Add changes to internal service and gateway for token revocation  events
  • Loading branch information
npamudika authored Feb 15, 2024
2 parents e6fb74c + e148c16 commit 0b7ae0c
Show file tree
Hide file tree
Showing 42 changed files with 1,650 additions and 201 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -190,5 +190,8 @@ public class APIMgtGatewayConstants {

//This will be a reserved name for the synapse message context properties.
public static final String ADDITIONAL_ANALYTICS_PROPS = "ADDITIONAL_ANALYTICS_PROPS_TO_PUBLISH";

public static final String AZP_JWT_CLAIM = "azp";
public static final String ENTITY_ID_JWT_CLAIM = "entity_id";
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com).
*
* 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 RevokedEventsDTO {

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

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

@SerializedName("revokedJWTSubjectEntityList")
private List<RevokedJWTSubjectEntityDTO> revokedJWTSubjectEntityList;

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<RevokedJWTSubjectEntityDTO> getRevokedSubjectEntityList() {
return revokedJWTSubjectEntityList;
}

public void setRevokedSubjectEntityList(List<RevokedJWTSubjectEntityDTO> subjectEntityDTOList) {
this.revokedJWTSubjectEntityList = subjectEntityDTOList;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com).
*
* 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 Consumer key.
*/
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) 2024, WSO2 LLC. (https://www.wso2.com).
*
* 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 Subject Entity.
*/
public class RevokedJWTSubjectEntityDTO {

@SerializedName("entity_id")
private String entityId;
@SerializedName("entity_type")
private String entityType;
@SerializedName("revocation_time")
private Long revocationTime;
@SerializedName("organization")
private String organization;

public String getEntityId() {
return entityId;
}

public void setEntityId(String entityId) {
this.entityId = entityId;
}

public String getEntityType() {
return entityType;
}

public void setEntityType(String entityType) {
this.entityType = entityType;
}

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 @@ -62,6 +62,7 @@
import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration;

import java.security.cert.Certificate;
import java.text.ParseException;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
Expand Down Expand Up @@ -177,7 +178,48 @@ public AuthenticationContext authenticate(SignedJWTInfo signedJWTInfo, MessageCo
"Invalid JWT token");
}
}

Object authorizedPartyClaim = signedJWTInfo.getJwtClaimsSet().getClaim(APIMgtGatewayConstants.AZP_JWT_CLAIM);
Object entityIdClaim = signedJWTInfo.getJwtClaimsSet().getClaim(APIMgtGatewayConstants.ENTITY_ID_JWT_CLAIM);
long jwtGeneratedTime = 0;
try {
jwtGeneratedTime = signedJWTInfo.getSignedJWT().getJWTClaimsSet().getIssueTime().getTime();
} catch (ParseException e) {
log.error("Error while obtaining JWT token generated time " + GatewayUtils.getMaskedToken(jwtHeader));
}
if (jwtGeneratedTime != 0 && authorizedPartyClaim != null && entityIdClaim != null) {
String authorizedParty = (String) authorizedPartyClaim;
String entityId = (String) entityIdClaim;
if (RevokedJWTDataHolder.getInstance().isRevokedConsumerKeyExists(authorizedParty, 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 (StringUtils.equals(entityId, authorizedParty)
&& RevokedJWTDataHolder.getInstance().isRevokedSubjectEntityConsumerAppExists(
entityId, 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 (!StringUtils.equals(entityId, authorizedParty) && RevokedJWTDataHolder.getInstance()
.isRevokedSubjectEntityUserExists(entityId, 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);

if (jwtValidationInfo != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;

Expand All @@ -30,6 +31,10 @@ public class RevokedJWTDataHolder {

private static final Log log = LogFactory.getLog(RevokedJWTDataHolder.class);
private static Map<String, Long> revokedJWTMap = new ConcurrentHashMap<>();
private static final Map<String, Long> revokedConsumerKeyMap = new ConcurrentHashMap<>();
private static final Map<String, Long> revokedSubjectEntityAppMap = new ConcurrentHashMap<>();
// User UUID (jwt claim) -> revoked timestamp
private static final Map<String, Long> revokedSubjectEntityUserMap = new ConcurrentHashMap<>();
private static RevokedJWTDataHolder instance = new RevokedJWTDataHolder();

/**
Expand Down Expand Up @@ -72,4 +77,65 @@ Map<String, Long> getRevokedJWTMap() {
public static RevokedJWTDataHolder getInstance() {
return instance;
}

public void addRevokedConsumerKeyToMap(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);
}
revokedConsumerKeyMap.put(consumerKey, revocationTime);
}

public boolean isRevokedConsumerKeyExists(String consumerKey, Long jwtGeneratedTimestamp) {

Long jwtRevokedTime = revokedConsumerKeyMap.get(consumerKey);

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

public void addRevokedSubjectEntityConsumerAppToMap(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);
}
revokedSubjectEntityAppMap.put(consumerKey, revocationTime);
}

public boolean isRevokedSubjectEntityConsumerAppExists(String consumerKey, Long jwtGeneratedTimestamp) {

Long jwtRevokedTime = revokedSubjectEntityAppMap.get(consumerKey);

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

return false;
}

public void addRevokedSubjectEntityUserToMap(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);
}
revokedSubjectEntityUserMap.put(userUUID, revocationTime);
}

public boolean isRevokedSubjectEntityUserExists(String user, Long jwtGeneratedTimestamp) {

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

0 comments on commit 0b7ae0c

Please sign in to comment.