001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk.jarm;
019
020
021import java.util.*;
022
023import com.nimbusds.jwt.*;
024import com.nimbusds.oauth2.sdk.AuthorizationResponse;
025import com.nimbusds.oauth2.sdk.ParseException;
026import com.nimbusds.oauth2.sdk.id.ClientID;
027import com.nimbusds.oauth2.sdk.id.Issuer;
028import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils;
029
030
031/**
032 * JWT Secured Authorization Response Mode for OAuth 2.0 (JARM) utilities.
033 */
034public final class JARMUtils {
035        
036        
037        /**
038         * Creates a JSON Web Token (JWT) claims set for the specified
039         * authorisation success response.
040         *
041         * @param iss      The OAuth 2.0 authorisation server issuer. Must not
042         *                 be {@code null}.
043         * @param aud      The client ID. Must not be {@code null}.
044         * @param exp      The JWT expiration time. Must not be {@code null}.
045         * @param response The plain authorisation response to use its
046         *                 parameters. Must not be {@code null}.
047         *
048         * @return The JWT claims set.
049         */
050        public static JWTClaimsSet toJWTClaimsSet(final Issuer iss,
051                                                  final ClientID aud,
052                                                  final Date exp,
053                                                  final AuthorizationResponse response) {
054        
055                if (exp == null) {
056                        throw new IllegalArgumentException("The expiration time must not be null");
057                }
058                
059                JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder()
060                        .issuer(iss.getValue())
061                        .audience(aud.getValue())
062                        .expirationTime(exp);
063                
064                for (Map.Entry<String, ?> en: MultivaluedMapUtils.toSingleValuedMap(response.toParameters()).entrySet()) {
065                        
066                        if ("response".equals(en.getKey())) {
067                                continue; // own JARM parameter, skip
068                        }
069                        
070                        builder = builder.claim(en.getKey(), en.getValue() + ""); // force string
071                }
072                
073                return builder.build();
074        }
075        
076        
077        /**
078         * Returns a multi-valued map representation of the specified JWT
079         * claims set.
080         *
081         * @param jwtClaimsSet The JWT claims set. Must not be {@code null}.
082         *
083         * @return The multi-valued map.
084         */
085        public static Map<String,List<String>> toMultiValuedStringParameters(final JWTClaimsSet jwtClaimsSet) {
086                
087                Map<String,List<String>> params = new HashMap<>();
088                
089                for (Map.Entry<String,Object> en: jwtClaimsSet.getClaims().entrySet()) {
090                        params.put(en.getKey(), Collections.singletonList(en.getValue() + ""));
091                }
092                
093                return params;
094        }
095        
096        
097        /**
098         * Returns {@code true} if the specified JWT-secured authorisation
099         * response implies an error response. Note that the JWT is not
100         * validated in any way!
101         *
102         * @param jwtString The JWT-secured authorisation response string. Must
103         *                  not be {@code null}.
104         *
105         * @return {@code true} if an error is implied by the presence of the
106         *         {@code error} claim, else {@code false} (also for encrypted
107         *         JWTs which payload cannot be inspected without decrypting
108         *         first).
109         *
110         * @throws ParseException If the JWT is invalid or plain (unsecured).
111         */
112        public static boolean impliesAuthorizationErrorResponse(final String jwtString)
113                throws ParseException  {
114                
115                try {
116                        return impliesAuthorizationErrorResponse(JWTParser.parse(jwtString));
117                } catch (java.text.ParseException e) {
118                        throw new ParseException("Invalid JWT-secured authorization response: " + e.getMessage(), e);
119                }
120        }
121        
122        
123        /**
124         * Returns {@code true} if the specified JWT-secured authorisation
125         * response implies an error response. Note that the JWT is not
126         * validated in any way!
127         *
128         * @param jwt The JWT-secured authorisation response. Must not be
129         *            {@code null}.
130         *
131         * @return {@code true} if an error is implied by the presence of the
132         *         {@code error} claim, else {@code false} (also for encrypted
133         *         JWTs which payload cannot be inspected without decrypting
134         *         first).
135         *
136         * @throws ParseException If the JWT is plain (unsecured).
137         */
138        public static boolean impliesAuthorizationErrorResponse(final JWT jwt)
139                throws ParseException  {
140                
141                if (jwt instanceof PlainJWT) {
142                        throw new ParseException("Invalid JWT-secured authorization response: The JWT must not be plain (unsecured)");
143                }
144                
145                if (jwt instanceof EncryptedJWT) {
146                        // Cannot peek into payload
147                        return false;
148                }
149                
150                if (jwt instanceof SignedJWT) {
151                        
152                        SignedJWT signedJWT = (SignedJWT)jwt;
153                        
154                        try {
155                                return signedJWT.getJWTClaimsSet().getStringClaim("error") != null;
156                        } catch (java.text.ParseException e) {
157                                throw new ParseException("Invalid JWT claims set: " + e.getMessage());
158                        }
159                }
160                
161                throw new ParseException("Unexpected JWT type");
162        }
163        
164        
165        /**
166         * Prevents public instantiation.
167         */
168        private JARMUtils() {}
169}