Skip to content

Commit 8781ffd

Browse files
author
Pedro González Marcos
committed
ref: extract pricingSecretKey field and add javadocs
1 parent dc9b6bf commit 8781ffd

File tree

5 files changed

+94
-71
lines changed

5 files changed

+94
-71
lines changed

src/main/java/io/github/isagroup/PricingContext.java

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,44 @@ public abstract class PricingContext {
2929
private static final Logger logger = LoggerFactory.getLogger(PricingContext.class);
3030

3131
/**
32-
* Returns path of the pricing configuration YAML file.
33-
* This file should be located in the resources folder, and the path should be
34-
* relative to it.
32+
* Returns the path to the Pricing2Yaml configuration file. The {@link String}
33+
* path given to the implementation of this method should point to a
34+
* Pricing2Yaml file under {@code resources} folder.
3535
*
36-
* @return Configuration file path
36+
* @return a path as {@link String} to a Pricing2Yaml configuration file
37+
* relative to the {@code resources} folder
3738
*/
3839
public abstract String getConfigFilePath();
3940

4041
/**
41-
* Returns the secret used to encode the pricing JWT.
42-
* * @return JWT secret String
42+
* Returns the secret used to sign the pricing JWT. The secret key needs to be
43+
* encoded in {@code base64}. Internally JWT library will choose the best
44+
* algorithm to sign the JWT ({@code HS256}, {@code HS384} or {@code HS512}), if
45+
* the secret key bit length does not conform to the minimun stated by these
46+
* algorithms will throw a {@link io.jsonwebtoken.security.WeakKeyException}
47+
*
48+
* @return a pricing secret encoded in {@code base64}
49+
* @see <a href=
50+
* "https://github.com/jwtk/jjwt#signature-algorithms-keys">Signature
51+
* Algorithm Keys</a>
52+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7518#section-3.2">HMAC
53+
* with SHA-2 Functions</a>
4354
*/
4455
public abstract String getJwtSecret();
4556

4657
/**
47-
* Returns the secret used to encode the authorization JWT.
48-
* * @return JWT secret String
58+
* Returns the secret used to sign the authorization JWT. The secret key needs
59+
* to be encoded in {@code base64}. Internally JWT library will choose the best
60+
* algorithm to sign the JWT ({@code HS256}, {@code HS384} or {@code HS512}), if
61+
* the secret key bit length does not conform to the minimun stated by these
62+
* algorithms will throw a {@link io.jsonwebtoken.security.WeakKeyException}
63+
*
64+
* @return a pricing secret encoded in {@code base64}
65+
* @see <a href=
66+
* "https://github.com/jwtk/jjwt#signature-algorithms-keys">Signature
67+
* Algorithm Keys</a>
68+
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7518#section-3.2">HMAC
69+
* with SHA-2 Functions</a>
4970
*/
5071
public String getAuthJwtSecret() {
5172
return this.getJwtSecret();
@@ -66,7 +87,8 @@ public int getJwtExpiration() {
6687
* for them.
6788
*
6889
* @return A {@link Boolean} indicating the condition to include, or not,
69-
* the pricing evaluation context in the JWT.
90+
* the pricing evaluation context in the JWT. Set to {@code true} by
91+
* default
7092
*
7193
* @see PricingEvaluatorUtil#generateUserToken
7294
*
@@ -77,9 +99,8 @@ public Boolean userAffectedByPricing() {
7799

78100
/**
79101
* This method should return the user context that will be used to evaluate the
80-
* pricing plan.
81-
* It should be considered which users has accessed the service and what
82-
* information is available.
102+
* pricing plan. It should be considered which users has accessed the service
103+
* and what information is available.
83104
*
84105
* @return Map with the user context
85106
*/
@@ -90,30 +111,35 @@ public Boolean userAffectedByPricing() {
90111
* With this information, the library will be able to build the {@link Plan}
91112
* object of the user from the configuration.
92113
*
93-
* @return String with the current user's plan name
114+
* @return a {@link String} with the current user's plan name
94115
*/
95116
public abstract String getUserPlan();
96117

97118
/**
98119
* This method should return a list with the name of the add-ons contracted by
99-
* the current user. If the pricing don't include add-ons, then just return an empty array.
100-
* With this information, the library will be able to build the subscription of
101-
* the user from the configuration.
120+
* the current user. If the pricing does not include add-ons, then just return
121+
* an empty {@link List}. With this information, the library will be able to
122+
* build the subscription of the user from the configuration.
102123
*
103-
* @return {@code List<String>} with the current user's contracted add-ons. Add-on names
104-
* should be the same as in the pricing configuration file.
124+
* @return a list with the current user's contracted add-ons.
125+
* Add-on names should be the same as in the pricing configuration file.
105126
*
106127
*/
107128
public abstract List<String> getUserAddOns();
108129

109130
/**
110131
* Returns a list with the full subscription contracted by the current user
111132
* (including plans and add-ons).
133+
* <p>
134+
* There are two keys inside this {@link Map}:
135+
* <ul>
136+
* <li>Key {@code plans} contains the plan name of the user
137+
* <li>Key {@code addOns} contains a list with the add-ons contracted by the
138+
* user
139+
* </ul>
112140
*
113-
* Key "plan" contains the plan name of the user.
114-
* Key "addOns" contains a list with the add-ons contracted by the user.
115-
*
116-
* @return {@code Map<String, Object>} with the current user's contracted subscription.
141+
* @return {@code Map<String, Object>} with the current user's contracted
142+
* subscription.
117143
*/
118144
public final Map<String, Object> getUserSubscription() {
119145
Map<String, Object> userSubscription = new HashMap<>();
@@ -164,7 +190,7 @@ public final Map<String, Object> getPlanContext() {
164190
* This method returns the {@link PricingManager} object that is being used to
165191
* evaluate the pricing plan.
166192
*
167-
* @return PricingManager object
193+
* @return {@link PricingManager} object
168194
*/
169195
public final PricingManager getPricingManager() {
170196
try {

src/main/java/io/github/isagroup/services/jwt/PricingJwtUtils.java

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import java.util.Date;
44
import java.util.Map;
55

6+
import javax.crypto.SecretKey;
7+
68
import org.slf4j.Logger;
79
import org.slf4j.LoggerFactory;
810
import org.springframework.beans.factory.annotation.Autowired;
@@ -25,16 +27,33 @@ public class PricingJwtUtils {
2527
@Autowired
2628
private PricingContext pricingContext;
2729

30+
private final SecretKey pricingSecretKey;
31+
2832
public PricingJwtUtils(PricingContext pricingContext) {
2933
this.pricingContext = pricingContext;
34+
this.pricingSecretKey = createKeyForBase64String(pricingContext.getJwtSecret());
35+
}
36+
37+
/**
38+
* Given a base64 encoded {@link String} creates a {@link SecretKey} to be used
39+
* when signing a JWT token. Given string must meet HMAC-SHA bit length
40+
* requirements
41+
*
42+
* @param base64String
43+
* @return {@link SecretKey}
44+
* @throws {@link WeakKeyException} if the string is not strong enough to
45+
* be used with HMAC-SHA algorithms
46+
*/
47+
private static SecretKey createKeyForBase64String(String base64String) {
48+
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(base64String));
3049
}
3150

3251
private static final Logger LOGGER = LoggerFactory.getLogger(PricingJwtUtils.class);
3352

3453
/**
3554
* Given a map claims and subject creates a JWT token given
3655
* {@link PricingContext} configuation
37-
*
56+
*
3857
* @param claims a {@link Map} of claims
3958
* @param subject a target of the token
4059
* @return The subject of the JWT
@@ -46,7 +65,7 @@ public String createJwtToken(Map<String, ?> claims, String subject) {
4665
.subject(subject)
4766
.issuedAt(new Date(System.currentTimeMillis()))
4867
.expiration(new Date(System.currentTimeMillis() + pricingContext.getJwtExpiration()))
49-
.signWith(Keys.hmacShaKeyFor(Decoders.BASE64.decode(pricingContext.getJwtSecret())))
68+
.signWith(pricingSecretKey)
5069
.compact();
5170
}
5271

@@ -57,7 +76,7 @@ public String createJwtToken(Map<String, ?> claims, String subject) {
5776
* @return The subject of the JWT
5877
*/
5978
public String getSubjectFromJwtToken(String token) {
60-
return Jwts.parser().verifyWith(Keys.hmacShaKeyFor(Decoders.BASE64.decode((pricingContext.getJwtSecret()))))
79+
return Jwts.parser().verifyWith(pricingSecretKey)
6180
.build()
6281
.parseSignedClaims(token).getPayload().getSubject();
6382
}
@@ -73,7 +92,7 @@ public String getSubjectFromJwtToken(String token) {
7392
*/
7493
public Map<String, Map<String, Object>> getFeaturesFromJwtToken(String token) {
7594
return (Map<String, Map<String, Object>>) Jwts.parser()
76-
.verifyWith(Keys.hmacShaKeyFor(Decoders.BASE64.decode((pricingContext.getJwtSecret())))).build()
95+
.verifyWith(pricingSecretKey).build()
7796
.parseSignedClaims(token)
7897
.getPayload().get("features");
7998
}
@@ -89,7 +108,7 @@ public Map<String, Map<String, Object>> getFeaturesFromJwtToken(String token) {
89108
*/
90109
public Map<String, Object> getPlanContextFromJwtToken(String token) {
91110
return (Map<String, Object>) Jwts.parser()
92-
.verifyWith(Keys.hmacShaKeyFor(Decoders.BASE64.decode((pricingContext.getJwtSecret())))).build()
111+
.verifyWith(pricingSecretKey).build()
93112
.parseSignedClaims(token)
94113
.getPayload().get("planContext");
95114
}
@@ -105,7 +124,7 @@ public Map<String, Object> getPlanContextFromJwtToken(String token) {
105124
*/
106125
public Map<String, Object> getUserContextFromJwtToken(String token) {
107126
return (Map<String, Object>) Jwts.parser()
108-
.verifyWith(Keys.hmacShaKeyFor(Decoders.BASE64.decode((pricingContext.getJwtSecret())))).build()
127+
.verifyWith(pricingSecretKey).build()
109128
.parseSignedClaims(token)
110129
.getPayload().get("userContext");
111130
}
@@ -119,7 +138,7 @@ public Map<String, Object> getUserContextFromJwtToken(String token) {
119138
*/
120139
public boolean validateJwtToken(String authToken) {
121140
try {
122-
Jwts.parser().verifyWith(Keys.hmacShaKeyFor(Decoders.BASE64.decode((pricingContext.getAuthJwtSecret()))))
141+
Jwts.parser().verifyWith(createKeyForBase64String(pricingContext.getAuthJwtSecret()))
123142
.build()
124143
.parseSignedClaims(authToken);
125144
return true;

src/test/java/io/github/isagroup/PricingContextTestImpl.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44
import java.util.List;
55
import java.util.Map;
66

7+
import io.jsonwebtoken.Jwts;
8+
import io.jsonwebtoken.io.Encoders;
9+
710
public class PricingContextTestImpl extends PricingContext {
811

12+
static final String JWT_SUBJECT_TEST = "admin1";
13+
914
private String path;
1015
private String secret;
1116
private Integer jwtExpiration;
@@ -14,12 +19,12 @@ public class PricingContextTestImpl extends PricingContext {
1419
private Map<String, Object> userContext;
1520

1621
public PricingContextTestImpl() {
17-
this.path = null;
18-
this.secret = "defualtSecret";
22+
this.path = "pricing/petclinic.yml";
23+
this.secret = Encoders.BASE64.encode(Jwts.SIG.HS256.key().build().getEncoded());
1924
this.jwtExpiration = 86400;
20-
this.userPlan = null;
25+
this.userPlan = "ADVANCED";
2126
this.userAddOns = new ArrayList<>();
22-
this.userContext = null;
27+
this.userContext = Map.of("username", JWT_SUBJECT_TEST, "pets", 2);
2328
}
2429

2530
@Override

src/test/java/io/github/isagroup/PricingEvaluatorUtilTests.java

Lines changed: 7 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package io.github.isagroup;
22

3-
import java.util.HashMap;
43
import java.util.Map;
54

6-
import org.junit.jupiter.api.BeforeEach;
75
import org.junit.jupiter.api.Test;
86

97
import io.github.isagroup.services.jwt.PricingJwtUtils;
@@ -12,39 +10,11 @@
1210

1311
public class PricingEvaluatorUtilTests {
1412

15-
private static final String JWT_SECRET_TEST = "qfqj73ZGIN1XxPvI5mG6dVaXqpY4XVeOOBjp4zf0yNE=";
16-
private static final Integer JWT_EXPIRATION_TEST = 86400;
17-
private static final String JWT_SUBJECT_TEST = "admin1";
18-
private static final String JWT_EXPRESSION_TEST = "userContext['pets']*4 < planContext['usageLimits']['pets']";
13+
private PricingContext pricingContext = new PricingContextTestImpl();
1914

20-
private static final String USER_PLAN = "ADVANCED";
21-
private static final String YAML_CONFIG_PATH = "pricing/petclinic.yml";
15+
private PricingEvaluatorUtil pricingEvaluatorUtil = new PricingEvaluatorUtil(pricingContext);
2216

23-
private PricingContext pricingContext;
24-
25-
private PricingEvaluatorUtil pricingEvaluatorUtil;
26-
27-
private PricingJwtUtils jwtUtils;
28-
29-
@BeforeEach
30-
public void setUp() {
31-
32-
Map<String, Object> userContext = new HashMap<>();
33-
userContext.put("username", JWT_SUBJECT_TEST);
34-
userContext.put("pets", 2);
35-
36-
PricingContextTestImpl pricingContext = new PricingContextTestImpl();
37-
38-
pricingContext.setJwtExpiration(JWT_EXPIRATION_TEST);
39-
pricingContext.setJwtSecret(JWT_SECRET_TEST);
40-
pricingContext.setUserContext(userContext);
41-
pricingContext.setUserPlan(USER_PLAN);
42-
pricingContext.setConfigFilePath(YAML_CONFIG_PATH);
43-
44-
this.pricingContext = pricingContext;
45-
this.pricingEvaluatorUtil = new PricingEvaluatorUtil(pricingContext);
46-
this.jwtUtils = new PricingJwtUtils(pricingContext);
47-
}
17+
private PricingJwtUtils jwtUtils = new PricingJwtUtils(pricingContext);
4818

4919
@Test
5020
void simpleTokenGenerationTest() {
@@ -71,22 +41,23 @@ void checkTokenSubjectTest() {
7141
String jwtSubject = jwtUtils.getSubjectFromJwtToken(token);
7242

7343
assertTrue(jwtUtils.validateJwtToken(token), "Token is not valid");
74-
assertEquals(JWT_SUBJECT_TEST, jwtSubject, "The subject has not being correctly set");
44+
assertEquals(PricingContextTestImpl.JWT_SUBJECT_TEST, jwtSubject, "The subject has not being correctly set");
7545

7646
}
7747

7848
@Test
7949
void tokenExpressionsTest() {
8050

8151
String firstToken = pricingEvaluatorUtil.generateUserToken();
52+
String jwtExpressionTest = "userContext['pets']*4 < planContext['usageLimits']['pets']";
8253

8354
String newToken = pricingEvaluatorUtil.addExpressionToToken(firstToken, "visits",
84-
JWT_EXPRESSION_TEST);
55+
jwtExpressionTest);
8556

8657
Map<String, Map<String, Object>> features = jwtUtils.getFeaturesFromJwtToken(newToken);
8758

8859
assertTrue(jwtUtils.validateJwtToken(newToken), "Token is not valid");
89-
assertEquals(JWT_EXPRESSION_TEST, (String) features.get("visits").get("eval"),
60+
assertEquals(jwtExpressionTest, (String) features.get("visits").get("eval"),
9061
"The expression for the feature visits has not being correctly set");
9162

9263
}

src/test/java/io/github/isagroup/pricingcontext/PricingConfigExpirationTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import io.github.isagroup.PricingContext;
1414
import io.github.isagroup.PricingEvaluatorUtil;
1515
import io.github.isagroup.services.jwt.PricingJwtUtils;
16+
import io.jsonwebtoken.Jwts;
17+
import io.jsonwebtoken.io.Encoders;
1618

1719
public class PricingConfigExpirationTest {
1820

@@ -38,7 +40,7 @@ public String getConfigFilePath() {
3840

3941
@Override
4042
public String getJwtSecret() {
41-
return "qfqj73ZGIN1XxPvI5mG6dVaXqpY4XVeOOBjp4zf0yNE=";
43+
return Encoders.BASE64.encode(Jwts.SIG.HS256.key().build().getEncoded());
4244
}
4345

4446
@Override

0 commit comments

Comments
 (0)