|
18 | 18 |
|
19 | 19 | import static org.apache.solr.security.jwt.JWTAuthPlugin.JWTAuthenticationResponse.AuthCode.AUTZ_HEADER_PROBLEM; |
20 | 20 | import static org.apache.solr.security.jwt.JWTAuthPlugin.JWTAuthenticationResponse.AuthCode.CLAIM_MISMATCH; |
| 21 | +import static org.apache.solr.security.jwt.JWTAuthPlugin.JWTAuthenticationResponse.AuthCode.JWT_EXPIRED; |
21 | 22 | import static org.apache.solr.security.jwt.JWTAuthPlugin.JWTAuthenticationResponse.AuthCode.JWT_VALIDATION_EXCEPTION; |
22 | 23 | import static org.apache.solr.security.jwt.JWTAuthPlugin.JWTAuthenticationResponse.AuthCode.NO_AUTZ_HEADER; |
23 | 24 | import static org.apache.solr.security.jwt.JWTAuthPlugin.JWTAuthenticationResponse.AuthCode.PASS_THROUGH; |
|
52 | 53 | import org.jose4j.jws.AlgorithmIdentifiers; |
53 | 54 | import org.jose4j.jws.JsonWebSignature; |
54 | 55 | import org.jose4j.jwt.JwtClaims; |
| 56 | +import org.jose4j.jwt.NumericDate; |
55 | 57 | import org.jose4j.keys.BigEndianBigInteger; |
56 | 58 | import org.jose4j.lang.JoseException; |
57 | 59 | import org.junit.After; |
@@ -746,4 +748,92 @@ public void testRegisterTokenEndpointForCsp() { |
746 | 748 | "http://acmepaymentscorp/oauth/oauth20/token", |
747 | 749 | EnvUtils.getProperty(LoadAdminUiServlet.SYSPROP_CSP_CONNECT_SRC_URLS)); |
748 | 750 | } |
| 751 | + |
| 752 | + @Test |
| 753 | + public void requireIssuerFalseButIssPresentAndMismatches() { |
| 754 | + // requireIssuer=false controls whether iss must be present, not whether a mismatching value |
| 755 | + // is silently accepted. A token with iss="IDServer" should fail when iss="NA" is configured. |
| 756 | + testConfig.put("iss", "NA"); |
| 757 | + testConfig.put("requireIss", false); |
| 758 | + plugin.init(testConfig); |
| 759 | + JWTAuthPlugin.JWTAuthenticationResponse resp = plugin.authenticate(testHeader); |
| 760 | + assertFalse(resp.isAuthenticated()); |
| 761 | + assertEquals(JWT_VALIDATION_EXCEPTION, resp.getAuthCode()); |
| 762 | + } |
| 763 | + |
| 764 | + @Test |
| 765 | + public void requireIssuerFalseNoIssInTokenOrConfig() { |
| 766 | + // requireIssuer=false with no iss claim in token and no iss in config → authenticated |
| 767 | + testConfig.put("requireIss", false); |
| 768 | + testConfig.put("requireExp", false); |
| 769 | + plugin.init(testConfig); |
| 770 | + JWTAuthPlugin.JWTAuthenticationResponse resp = plugin.authenticate(slimHeader); |
| 771 | + assertTrue(resp.getErrorMessage(), resp.isAuthenticated()); |
| 772 | + } |
| 773 | + |
| 774 | + @Test |
| 775 | + public void scopeClaimAsJsonArray() throws Exception { |
| 776 | + // Verify that a scope claim expressed as a JSON array (not just a whitespace-separated String) |
| 777 | + // is correctly parsed: authentication succeeds and "openid" is filtered out of the roles. |
| 778 | + JwtClaims claims = generateClaims(); |
| 779 | + claims.setClaim("scope", Arrays.asList("solr:read", "openid")); |
| 780 | + JsonWebSignature jws = new JsonWebSignature(); |
| 781 | + jws.setPayload(claims.toJson()); |
| 782 | + jws.setKey(rsaJsonWebKey.getPrivateKey()); |
| 783 | + jws.setKeyIdHeaderValue(rsaJsonWebKey.getKeyId()); |
| 784 | + jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); |
| 785 | + String header = "Bearer " + jws.getCompactSerialization(); |
| 786 | + |
| 787 | + testConfig.put("scope", "solr:read"); |
| 788 | + plugin.init(testConfig); |
| 789 | + JWTAuthPlugin.JWTAuthenticationResponse resp = plugin.authenticate(header); |
| 790 | + assertTrue(resp.getErrorMessage(), resp.isAuthenticated()); |
| 791 | + Set<String> roles = ((VerifiedUserRoles) resp.getPrincipal()).getVerifiedRoles(); |
| 792 | + assertTrue(roles.contains("solr:read")); |
| 793 | + assertFalse("openid should be filtered from roles", roles.contains("openid")); |
| 794 | + } |
| 795 | + |
| 796 | + @Test |
| 797 | + public void tokenExpiredWithinClockSkewIsAuthenticated() throws Exception { |
| 798 | + // Token expired 25 seconds ago — within the 30-second clock skew tolerance. |
| 799 | + // All timestamps must be consistent: iat < exp, so iat is set 90 seconds in the past. |
| 800 | + NumericDate now = NumericDate.now(); |
| 801 | + JwtClaims claims = new JwtClaims(); |
| 802 | + claims.setIssuer("IDServer"); |
| 803 | + claims.setClaim("customPrincipal", "custom"); |
| 804 | + claims.setIssuedAt(NumericDate.fromSeconds(now.getValue() - 90)); |
| 805 | + claims.setNotBefore(NumericDate.fromSeconds(now.getValue() - 90)); |
| 806 | + claims.setExpirationTime(NumericDate.fromSeconds(now.getValue() - 25)); |
| 807 | + JsonWebSignature jws = new JsonWebSignature(); |
| 808 | + jws.setPayload(claims.toJson()); |
| 809 | + jws.setKey(rsaJsonWebKey.getPrivateKey()); |
| 810 | + jws.setKeyIdHeaderValue(rsaJsonWebKey.getKeyId()); |
| 811 | + jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); |
| 812 | + String header = "Bearer " + jws.getCompactSerialization(); |
| 813 | + |
| 814 | + JWTAuthPlugin.JWTAuthenticationResponse resp = plugin.authenticate(header); |
| 815 | + assertTrue(resp.getErrorMessage(), resp.isAuthenticated()); |
| 816 | + } |
| 817 | + |
| 818 | + @Test |
| 819 | + public void tokenExpiredBeyondClockSkewIsRejected() throws Exception { |
| 820 | + // Token expired 35 seconds ago — beyond the 30-second clock skew tolerance. |
| 821 | + NumericDate now = NumericDate.now(); |
| 822 | + JwtClaims claims = new JwtClaims(); |
| 823 | + claims.setIssuer("IDServer"); |
| 824 | + claims.setClaim("customPrincipal", "custom"); |
| 825 | + claims.setIssuedAt(NumericDate.fromSeconds(now.getValue() - 90)); |
| 826 | + claims.setNotBefore(NumericDate.fromSeconds(now.getValue() - 90)); |
| 827 | + claims.setExpirationTime(NumericDate.fromSeconds(now.getValue() - 35)); |
| 828 | + JsonWebSignature jws = new JsonWebSignature(); |
| 829 | + jws.setPayload(claims.toJson()); |
| 830 | + jws.setKey(rsaJsonWebKey.getPrivateKey()); |
| 831 | + jws.setKeyIdHeaderValue(rsaJsonWebKey.getKeyId()); |
| 832 | + jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); |
| 833 | + String header = "Bearer " + jws.getCompactSerialization(); |
| 834 | + |
| 835 | + JWTAuthPlugin.JWTAuthenticationResponse resp = plugin.authenticate(header); |
| 836 | + assertFalse(resp.isAuthenticated()); |
| 837 | + assertEquals(JWT_EXPIRED, resp.getAuthCode()); |
| 838 | + } |
749 | 839 | } |
0 commit comments