001package com.nimbusds.openid.connect.provider.spi.tokens;
002
003
004import java.util.*;
005
006import net.jcip.annotations.ThreadSafe;
007import net.minidev.json.JSONObject;
008
009import com.nimbusds.jose.JOSEObjectType;
010import com.nimbusds.jwt.JWTClaimsSet;
011import com.nimbusds.oauth2.sdk.auth.X509CertificateConfirmation;
012import com.nimbusds.oauth2.sdk.id.*;
013
014
015/**
016 * Base implementation of the SPI for encoding and decoding authorisations for
017 * self-contained access tokens into JWT claims sets.
018 *
019 * <p>Provides encoding and decoding for all token parameters for which there
020 * is an appropriate standard JWT claim (see JSON Web Token (JWT) (RFC 7519),
021 * section 4.1, OAuth 2.0 Mutual-TLS Client Authentication and
022 * Certificate-Bound Access Tokens (RFC 8705), section 3.1):
023 *
024 * <ul>
025 *     <li>{@link AccessTokenAuthorization#getSubject subject} - "sub"
026 *     <li>{@link AccessTokenAuthorization#getActor actor} - "act"
027 *     <li>{@link AccessTokenAuthorization#getExpirationTime expiration time} - "exp"
028 *     <li>{@link AccessTokenAuthorization#getIssueTime issue time} - "iat"
029 *     <li>{@link AccessTokenAuthorization#getIssuer issuer} - "iss"
030 *     <li>{@link AccessTokenAuthorization#getAudienceList audience} - "aud"
031 *     <li>{@link AccessTokenAuthorization#getJWTID JWT ID} - "jti"
032 *     <li>{@link AccessTokenAuthorization#getClientCertificateConfirmation client X.509 certificate SHA-256 thumbprint (mTLS)} - "cnf.x5t#S256"
033 * </ul>
034 *
035 * <p>The extending class should implement encoding and decoding for the
036 * remaining token parameters:
037 *
038 * <ul>
039 *     <li>{@link AccessTokenAuthorization#getClientID client ID}
040 *     <li>{@link AccessTokenAuthorization#getScope scope}
041 *     <li>{@link AccessTokenAuthorization#getClaimNames consented OpenID claim names}
042 *     <li>{@link AccessTokenAuthorization#getClaimsLocales preferred claims locales}
043 *     <li>{@link AccessTokenAuthorization#getPresetClaims preset OpenID claims}
044 *     <li>{@link AccessTokenAuthorization#getClaimsData claims fulfillment data}
045 *     <li>{@link AccessTokenAuthorization#getData additional data}
046 *     <li>{@link AccessTokenAuthorization#getOtherTopLevelParameters other top-level parameters}
047 * </ul>
048 */
049@ThreadSafe
050public abstract class BaseSelfContainedAccessTokenClaimsCodec implements SelfContainedAccessTokenClaimsCodec {
051        
052        
053        /**
054         * The supported claim names.
055         */
056        public static final Set<String> SUPPORTED_CLAIM_NAMES = Collections.unmodifiableSet(
057                new HashSet<>(Arrays.asList(
058                        "sub", "act", "exp", "iat", "iss", "aud", "jti", "cnf"
059                ))
060        );
061        
062        
063        @Override
064        public JWTClaimsSet encode(final AccessTokenAuthorization tokenAuthz, final TokenEncoderContext context) {
065                
066                JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
067                
068                if (tokenAuthz.getSubject()        != null) builder.subject(tokenAuthz.getSubject().getValue());
069                if (tokenAuthz.getActor()          != null) builder.claim("act", tokenAuthz.getActor().toJSONObject());
070                if (tokenAuthz.getExpirationTime() != null) builder.expirationTime(Date.from(tokenAuthz.getExpirationTime()));
071                if (tokenAuthz.getIssueTime()      != null) builder.issueTime(Date.from(tokenAuthz.getIssueTime()));
072                if (tokenAuthz.getIssuer()         != null) builder.issuer(tokenAuthz.getIssuer().getValue());
073                if (tokenAuthz.getAudienceList()   != null) builder.audience(Audience.toStringList(tokenAuthz.getAudienceList()));
074                if (tokenAuthz.getJWTID()          != null) builder.jwtID(tokenAuthz.getJWTID().getValue());
075                if (tokenAuthz.getClientCertificateConfirmation() != null) {
076                        Map.Entry<String, JSONObject> cnf = tokenAuthz.getClientCertificateConfirmation().toJWTClaim();
077                        builder.claim(cnf.getKey(), cnf.getValue());
078                }
079                
080                return builder.build();
081        }
082        
083        
084        @Override
085        public JWTDetails advancedEncode(final AccessTokenAuthorization tokenAuthz, final TokenEncoderContext context) {
086                return new JWTDetails() {
087                        @Override
088                        public JOSEObjectType getType() {
089                                return null;
090                        }
091                        
092                        
093                        @Override
094                        public JWTClaimsSet getJWTClaimsSet() {
095                                return encode(tokenAuthz, context);
096                        }
097                };
098        }
099        
100        
101        @Override
102        public AccessTokenAuthorization decode(final JWTClaimsSet claimsSet, final TokenCodecContext context)
103                throws TokenDecodeException {
104                
105                MutableAccessTokenAuthorization authz = new MutableAccessTokenAuthorization();
106                
107                String sub = claimsSet.getSubject();
108                if (sub != null) authz.withSubject(new Subject(sub));
109                
110                try {
111                        JSONObject act = claimsSet.getJSONObjectClaim("act");
112                        if (act != null) authz.withActor(Actor.parse(act));
113                } catch (Exception e) {
114                        throw new TokenDecodeException("Couldn't parse actor: " + e.getMessage(), e);
115                }
116                
117                Date exp = claimsSet.getExpirationTime();
118                if (exp != null) authz.withExpirationTime(exp.toInstant());
119                
120                Date iat = claimsSet.getIssueTime();
121                if (iat != null) authz.withIssueTime(iat.toInstant());
122                
123                String iss = claimsSet.getIssuer();
124                if (iss != null) authz.withIssuer(new Issuer(iss));
125                
126                List<String> aud = claimsSet.getAudience();
127                if (aud != null && ! aud.isEmpty()) authz.withAudienceList(Audience.create(aud));
128                
129                String jti = claimsSet.getJWTID();
130                if (jti != null) authz.withJWTID(new JWTID(jti));
131                
132                authz.withClientCertificateConfirmation(X509CertificateConfirmation.parse(claimsSet));
133                
134                return authz;
135        }
136        
137        
138        @Override
139        public AccessTokenAuthorization advancedDecode(final JWTDetails jwtDetails, final TokenCodecContext context)
140                throws TokenDecodeException {
141                
142                return decode(jwtDetails.getJWTClaimsSet(), context);
143        }
144}