Skip to content
This repository was archived by the owner on Apr 10, 2025. It is now read-only.

Commit 939b904

Browse files
authored
Nested PrivateClaims implementation (#21)
1 parent 29bc7c7 commit 939b904

File tree

5 files changed

+165
-43
lines changed

5 files changed

+165
-43
lines changed

GeneXusJWT/src/main/java/com/genexus/JWT/JWTCreator.java

Lines changed: 116 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.genexus.JWT;
22

3+
import java.util.ArrayList;
4+
import java.util.HashMap;
35
import java.util.List;
46
import java.util.Map;
57

@@ -11,6 +13,8 @@
1113
import com.auth0.jwt.interfaces.DecodedJWT;
1214
import com.auth0.jwt.interfaces.JWTVerifier;
1315
import com.auth0.jwt.interfaces.Verification;
16+
import com.fasterxml.jackson.core.type.TypeReference;
17+
import com.fasterxml.jackson.databind.ObjectMapper;
1418
import com.genexus.JWT.claims.Claim;
1519
import com.genexus.JWT.claims.PrivateClaims;
1620
import com.genexus.JWT.claims.PublicClaims;
@@ -25,17 +29,18 @@
2529
import com.genexus.securityapicommons.keys.PrivateKeyManager;
2630
import com.genexus.securityapicommons.utils.SecurityUtils;
2731

28-
29-
3032
public class JWTCreator extends JWTObject {
3133

34+
private int counter;
35+
3236
public JWTCreator() {
3337
super();
3438
EncodingUtil eu = new EncodingUtil();
3539
eu.setEncoding("UTF8");
36-
40+
this.counter = 0;
41+
3742
}
38-
43+
3944
/******** EXTERNAL OBJECT PUBLIC METHODS - BEGIN ********/
4045
public String doCreate(String algorithm, PrivateClaims privateClaims, JWTOptions options) {
4146
if (options.hasError()) {
@@ -98,7 +103,7 @@ public boolean doVerify(String token, String expectedAlgorithm, PrivateClaims pr
98103
this.error.setError("JW005", e.getMessage());
99104
return false;
100105
}
101-
if (isRevoqued(decodedJWT, options) || !verifyPrivateClaims(decodedJWT, privateClaims)) {
106+
if (isRevoqued(decodedJWT, options) || !verifyPrivateClaims(decodedJWT, privateClaims, options)) {
102107
return false;
103108
}
104109
String algorithm = decodedJWT.getAlgorithm();
@@ -107,12 +112,11 @@ public boolean doVerify(String token, String expectedAlgorithm, PrivateClaims pr
107112
return false;
108113
}
109114
JWTAlgorithm expectedJWTAlgorithm = JWTAlgorithm.getJWTAlgorithm(expectedAlgorithm, this.error);
110-
if(alg.compareTo(expectedJWTAlgorithm) != 0 || this.hasError())
111-
{
112-
this.error.setError("JW008", "Expected algorithm does not match token algorithm");
113-
return false;
114-
}
115-
115+
if (alg.compareTo(expectedJWTAlgorithm) != 0 || this.hasError()) {
116+
this.error.setError("JW008", "Expected algorithm does not match token algorithm");
117+
return false;
118+
}
119+
116120
Algorithm algorithmType = null;
117121
if (JWTAlgorithm.isPrivate(alg)) {
118122
CertificateX509 cert = options.getCertificate();
@@ -150,7 +154,7 @@ public boolean doVerify(String token, String expectedAlgorithm, PrivateClaims pr
150154
error.setError("JW006", e.getMessage());
151155
return false;
152156
}
153-
157+
154158
return true;
155159

156160
}
@@ -236,7 +240,11 @@ private Builder doBuildPayload(Builder tokenBuilder, PrivateClaims privateClaims
236240
List<Claim> privateC = privateClaims.getAllClaims();
237241
for (int i = 0; i < privateC.size(); i++) {
238242
try {
239-
tokenBuilder.withClaim(privateC.get(i).getKey(), privateC.get(i).getValue());
243+
if (privateC.get(i).getNestedClaims() != null) {
244+
tokenBuilder.withClaim(privateC.get(i).getKey(), privateC.get(i).getNestedClaims().getNestedMap());
245+
} else {
246+
tokenBuilder.withClaim(privateC.get(i).getKey(), privateC.get(i).getValue());
247+
}
240248
} catch (Exception e) {
241249
this.error.setError("JW004", e.getMessage());
242250
return null;
@@ -275,32 +283,107 @@ private Builder doBuildPayload(Builder tokenBuilder, PrivateClaims privateClaims
275283
// ****END BUILD PAYLOAD****//
276284
return tokenBuilder;
277285
}
278-
279-
private boolean verifyPrivateClaims(DecodedJWT decodedJWT, PrivateClaims privateClaims)
280-
{
281-
if(privateClaims == null || privateClaims.isEmpty())
282-
{
286+
287+
private boolean verifyPrivateClaims(DecodedJWT decodedJWT, PrivateClaims privateClaims, JWTOptions options) {
288+
RegisteredClaims registeredClaims = options.getAllRegisteredClaims();
289+
PublicClaims publicClaims = options.getAllPublicClaims();
290+
if (privateClaims == null || privateClaims.isEmpty()) {
283291
return true;
284292
}
285-
Map<String, com.auth0.jwt.interfaces.Claim> map = decodedJWT.getClaims();
286-
287-
List<Claim> claims = privateClaims.getAllClaims();
288-
for(int i= 0; i < claims.size(); i++)
289-
{
290-
Claim c = claims.get(i);
291-
if(!map.containsKey(c.getKey()))
292-
{
293-
return false;
294-
}
295-
com.auth0.jwt.interfaces.Claim claim = map.get(c.getKey());
296-
if(!SecurityUtils.compareStrings(claim.asString().trim(), c.getValue().trim()))
297-
{
298-
return false;
293+
String base64Part = decodedJWT.getPayload();
294+
byte[] base64Bytes = Base64.decodeBase64(base64Part);
295+
EncodingUtil eu = new EncodingUtil();
296+
String plainTextPart = eu.getString(base64Bytes);
297+
HashMap<String, Object> map = new HashMap<String, Object>();
298+
ObjectMapper mapper = new ObjectMapper();
299+
300+
try {
301+
map = mapper.readValue(plainTextPart, new TypeReference<Map<String, Object>>() {
302+
});
303+
} catch (Exception e) {
304+
this.error.setError("JW009", "Cannot parse JWT payload");
305+
return false;
306+
}
307+
this.counter = 0;
308+
boolean validation = verifyNestedClaims(privateClaims.getNestedMap(), map, registeredClaims, publicClaims);
309+
int pClaimsCount = countingPrivateClaims(privateClaims.getNestedMap(), 0);
310+
if (validation && !(this.counter == pClaimsCount)) {
311+
return false;
312+
}
313+
return validation;
314+
}
315+
316+
private boolean verifyNestedClaims(Map<String, Object> pclaimMap, Map<String, Object> map,
317+
RegisteredClaims registeredClaims, PublicClaims publicClaims) {
318+
List<String> mapClaimKeyList = new ArrayList<String>(map.keySet());
319+
List<String> pClaimKeyList = new ArrayList<String>(pclaimMap.keySet());
320+
if (pClaimKeyList.size() > pClaimKeyList.size()) {
321+
return false;
322+
}
323+
for (String mapKey : mapClaimKeyList) {
324+
325+
if (!isRegistered(mapKey, registeredClaims) && !isPublic(mapKey, publicClaims)) {
326+
327+
this.counter++;
328+
if (!pclaimMap.containsKey(mapKey)) {
329+
return false;
330+
}
331+
332+
Object op = pclaimMap.get(mapKey);
333+
Object ot = map.get(mapKey);
334+
335+
if (op instanceof String && ot instanceof String) {
336+
337+
if (!SecurityUtils.compareStrings(((String) op).trim(), ((String) ot).trim())) {
338+
return false;
339+
}
340+
} else if (op instanceof HashMap && ot instanceof HashMap) {
341+
@SuppressWarnings("unchecked")
342+
boolean flag = verifyNestedClaims((HashMap<String, Object>) op, (HashMap<String, Object>) ot,
343+
registeredClaims, publicClaims);
344+
if (!flag) {
345+
return false;
346+
}
347+
} else {
348+
return false;
349+
}
299350
}
300351
}
301352
return true;
302353
}
303-
304354

355+
private boolean isRegistered(String claimKey, RegisteredClaims registeredClaims) {
356+
357+
List<Claim> registeredClaimsList = registeredClaims.getAllClaims();
358+
for (Claim s : registeredClaimsList) {
359+
if (SecurityUtils.compareStrings(s.getKey().trim(), claimKey.trim())) {
360+
return true;
361+
}
362+
}
363+
return false;
364+
}
365+
366+
private boolean isPublic(String claimKey, PublicClaims publicClaims) {
367+
List<Claim> publicClaimsList = publicClaims.getAllClaims();
368+
for (Claim s : publicClaimsList) {
369+
if (SecurityUtils.compareStrings(s.getKey().trim(), claimKey.trim())) {
370+
return true;
371+
}
372+
}
373+
return false;
374+
}
375+
376+
@SuppressWarnings("unchecked")
377+
private int countingPrivateClaims(Map<String, Object> map, int counter) {
378+
List<String> list = new ArrayList<String>(map.keySet());
379+
for (String s : list) {
380+
counter++;
381+
Object obj = map.get(s);
382+
if (obj instanceof HashMap) {
383+
counter = countingPrivateClaims((HashMap<String, Object>) obj, counter);
384+
}
385+
}
386+
return counter;
387+
}
305388

306389
}

GeneXusJWT/src/main/java/com/genexus/JWT/claims/Claim.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,27 @@
33
public class Claim {
44

55
private String key;
6-
private String value;
6+
private Object value;
77

8-
public Claim(String valueKey, String valueOfValue) {
8+
public Claim(String valueKey, Object valueOfValue) {
99
key = valueKey;
1010
value = valueOfValue;
11-
}
11+
}
1212

1313
public String getValue() {
14-
return value;
14+
if (value instanceof String) {
15+
return (String) value;
16+
} else {
17+
return null;
18+
}
19+
}
20+
21+
public PrivateClaims getNestedClaims() {
22+
if (value instanceof PrivateClaims) {
23+
return (PrivateClaims) value;
24+
} else {
25+
return null;
26+
}
1527
}
1628

1729
public String getKey() {

GeneXusJWT/src/main/java/com/genexus/JWT/claims/Claims.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import java.util.ArrayList;
44
import java.util.List;
55

6-
import com.genexus.JWT.utils.JWTUtils;
76
import com.genexus.securityapicommons.commons.Error;
87
import com.genexus.securityapicommons.utils.SecurityUtils;
98

@@ -13,9 +12,9 @@ public class Claims {
1312

1413
public Claims() {
1514
claims = new ArrayList<Claim>();
16-
}
15+
}
1716

18-
public boolean setClaim(String key, String value, Error error) {
17+
public boolean setClaim(String key, Object value, Error error) {
1918
Claim claim = new Claim(key, value);
2019
claims.add(claim);
2120
return true;
Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,42 @@
11
package com.genexus.JWT.claims;
22

3+
import java.util.HashMap;
34
import java.util.List;
5+
import java.util.Map;
46

57
import com.genexus.securityapicommons.commons.Error;
68

79
public final class PrivateClaims extends Claims {
810

911
private List<Claim> claims;
1012

13+
1114
public PrivateClaims() {
1215
super();
16+
1317
}
14-
18+
1519
public boolean setClaim(String key, String value) {
1620
return super.setClaim(key, value, new Error());
1721
}
22+
23+
public boolean setClaim(String key, PrivateClaims value)
24+
{
25+
26+
return super.setClaim(key, value, new Error());
27+
}
28+
29+
public Map<String, Object> getNestedMap() {
30+
HashMap<String, Object> result = new HashMap<String, Object>();
31+
for (Claim c : getAllClaims()) {
32+
if (c.getValue() != null) {
33+
result.put(c.getKey(), c.getValue());
34+
} else {
35+
result.put(c.getKey(), ((PrivateClaims) c.getNestedClaims()).getNestedMap());
36+
}
37+
}
38+
39+
return result;
40+
}
41+
1842
}

GeneXusJWT/src/main/java/com/genexus/JWT/claims/RegisteredClaims.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import java.util.HashMap;
44
import java.util.List;
55

6-
import com.genexus.JWT.utils.JWTUtils;
76
import com.genexus.securityapicommons.commons.Error;
87
import com.genexus.securityapicommons.utils.SecurityUtils;
98

@@ -15,10 +14,15 @@ public final class RegisteredClaims extends Claims {
1514
public RegisteredClaims() {
1615
super();
1716
customTimeValidationClaims = new HashMap<String, String>();
18-
17+
1918
}
2019

2120
@Override
21+
public boolean setClaim(String key, Object value, Error error) {
22+
error.setError("RC001", "Not alllowed data type");
23+
return false;
24+
}
25+
2226
public boolean setClaim(String key, String value, Error error) {
2327
if (RegisteredClaim.exists(key)) {
2428
return super.setClaim(key, value, error);

0 commit comments

Comments
 (0)