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, draft-ietf-oauth-mtls-17, section 3.1):
022 *
023 * <ul>
024 *     <li>{@link AccessTokenAuthorization#getSubject subject} - "sub"
025 *     <li>{@link AccessTokenAuthorization#getActor actor} - "act"
026 *     <li>{@link AccessTokenAuthorization#getExpirationTime expiration time} - "exp"
027 *     <li>{@link AccessTokenAuthorization#getIssueTime issue time} - "iat"
028 *     <li>{@link AccessTokenAuthorization#getIssuer issuer} - "iss"
029 *     <li>{@link AccessTokenAuthorization#getAudienceList audience} - "aud"
030 *     <li>{@link AccessTokenAuthorization#getJWTID JWT ID} - "jti"
031 *     <li>{@link AccessTokenAuthorization#getClientCertificateConfirmation client X.509 certificate SHA-256 thumbprint (mTLS)} - "cnf.x5t#S256"
032 * </ul>
033 *
034 * <p>The extending class should implement encoding and decoding for the
035 * remaining token parameters:
036 *
037 * <ul>
038 *     <li>{@link AccessTokenAuthorization#getClientID client ID}
039 *     <li>{@link AccessTokenAuthorization#getScope scope}
040 *     <li>{@link AccessTokenAuthorization#getClaimNames consented OpenID claim names}
041 *     <li>{@link AccessTokenAuthorization#getClaimsLocales preferred claims locales}
042 *     <li>{@link AccessTokenAuthorization#getPresetClaims preset OpenID claims}
043 *     <li>{@link AccessTokenAuthorization#getData additional data}
044 *     <li>{@link AccessTokenAuthorization#getOtherTopLevelParameters other top-level parameters}
045 * </ul>
046 */
047@ThreadSafe
048public abstract class BaseSelfContainedAccessTokenClaimsCodec implements SelfContainedAccessTokenClaimsCodec {
049        
050        
051        /**
052         * The supported claim names.
053         */
054        public static final Set<String> SUPPORTED_CLAIM_NAMES = Collections.unmodifiableSet(
055                new HashSet<>(Arrays.asList(
056                        "sub", "act", "exp", "iat", "iss", "aud", "jti", "cnf"
057                ))
058        );
059        
060        
061        @Override
062        public JWTClaimsSet encode(final AccessTokenAuthorization tokenAuthz, final TokenEncoderContext context) {
063                
064                JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
065                
066                if (tokenAuthz.getSubject()        != null) builder.subject(tokenAuthz.getSubject().getValue());
067                if (tokenAuthz.getActor()          != null) builder.claim("act", tokenAuthz.getActor().toJSONObject());
068                if (tokenAuthz.getExpirationTime() != null) builder.expirationTime(Date.from(tokenAuthz.getExpirationTime()));
069                if (tokenAuthz.getIssueTime()      != null) builder.issueTime(Date.from(tokenAuthz.getIssueTime()));
070                if (tokenAuthz.getIssuer()         != null) builder.issuer(tokenAuthz.getIssuer().getValue());
071                if (tokenAuthz.getAudienceList()   != null) builder.audience(Audience.toStringList(tokenAuthz.getAudienceList()));
072                if (tokenAuthz.getJWTID()          != null) builder.jwtID(tokenAuthz.getJWTID().getValue());
073                if (tokenAuthz.getClientCertificateConfirmation() != null) {
074                        Map.Entry<String, JSONObject> cnf = tokenAuthz.getClientCertificateConfirmation().toJWTClaim();
075                        builder.claim(cnf.getKey(), cnf.getValue());
076                }
077                
078                return builder.build();
079        }
080        
081        
082        @Override
083        public JWTDetails advancedEncode(final AccessTokenAuthorization tokenAuthz, final TokenEncoderContext context) {
084                return new JWTDetails() {
085                        @Override
086                        public JOSEObjectType getType() {
087                                return null;
088                        }
089                        
090                        
091                        @Override
092                        public JWTClaimsSet getJWTClaimsSet() {
093                                return encode(tokenAuthz, context);
094                        }
095                };
096        }
097        
098        
099        @Override
100        public AccessTokenAuthorization decode(final JWTClaimsSet claimsSet, final TokenCodecContext context)
101                throws TokenDecodeException {
102                
103                MutableAccessTokenAuthorization authz = new MutableAccessTokenAuthorization();
104                
105                String sub = claimsSet.getSubject();
106                if (sub != null) authz.withSubject(new Subject(sub));
107                
108                try {
109                        JSONObject act = claimsSet.getJSONObjectClaim("act");
110                        if (act != null) authz.withActor(Actor.parse(act));
111                } catch (Exception e) {
112                        throw new TokenDecodeException("Couldn't parse actor: " + e.getMessage(), e);
113                }
114                
115                Date exp = claimsSet.getExpirationTime();
116                if (exp != null) authz.withExpirationTime(exp.toInstant());
117                
118                Date iat = claimsSet.getIssueTime();
119                if (iat != null) authz.withIssueTime(iat.toInstant());
120                
121                String iss = claimsSet.getIssuer();
122                if (iss != null) authz.withIssuer(new Issuer(iss));
123                
124                List<String> aud = claimsSet.getAudience();
125                if (aud != null && ! aud.isEmpty()) authz.withAudienceList(Audience.create(aud));
126                
127                String jti = claimsSet.getJWTID();
128                if (jti != null) authz.withJWTID(new JWTID(jti));
129                
130                authz.withClientCertificateConfirmation(X509CertificateConfirmation.parse(claimsSet));
131                
132                return authz;
133        }
134        
135        
136        @Override
137        public AccessTokenAuthorization advancedDecode(final JWTDetails jwtDetails, final TokenCodecContext context)
138                throws TokenDecodeException {
139                
140                return decode(jwtDetails.getJWTClaimsSet(), context);
141        }
142}