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.openid.connect.sdk.claims;
019
020
021import java.util.*;
022
023import net.minidev.json.JSONArray;
024import net.minidev.json.JSONObject;
025
026import com.nimbusds.jwt.JWTClaimsSet;
027import com.nimbusds.oauth2.sdk.ParseException;
028import com.nimbusds.oauth2.sdk.id.Audience;
029import com.nimbusds.oauth2.sdk.id.Issuer;
030import com.nimbusds.oauth2.sdk.id.JWTID;
031import com.nimbusds.oauth2.sdk.id.Subject;
032import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
033
034
035/**
036 * Back-channel logout token claims set, serialisable to a JSON object.
037 *
038 * <p>Example logout token claims set:
039 *
040 * <pre>
041 * {
042 *   "iss"    : "https://server.example.com",
043 *   "sub"    : "248289761001",
044 *   "aud"    : "s6BhdRkqt3",
045 *   "iat"    : 1471566154,
046 *   "jti"    : "bWJq",
047 *   "sid"    : "08a5019c-17e1-4977-8f42-65a12843ea02",
048 *   "events" : { "http://schemas.openid.net/event/backchannel-logout": { } }
049 * }
050 * </pre>
051 *
052 * <p>Related specifications:
053 *
054 * <ul>
055 *     <li>OpenID Connect Back-Channel Logout 1.0, section 2.4 (draft 04).
056 *     <li>Security Event Token (SET) (RFC 8417).
057 * </ul>
058 */
059public class LogoutTokenClaimsSet extends CommonOIDCTokenClaimsSet {
060        
061        
062        /**
063         * The JWT ID claim name.
064         */
065        public static final String JTI_CLAIM_NAME = "jti";
066        
067        
068        /**
069         * The events claim name.
070         */
071        public static final String EVENTS_CLAIM_NAME = "events";
072        
073        
074        /**
075         * The OpenID logout event type.
076         */
077        public static final String EVENT_TYPE = "http://schemas.openid.net/event/backchannel-logout";
078        
079        
080        /**
081         * The names of the standard top-level ID token claims.
082         */
083        private static final Set<String> STD_CLAIM_NAMES;
084        
085        
086        static {
087                Set<String> claimNames = new HashSet<>(CommonOIDCTokenClaimsSet.getStandardClaimNames());
088                claimNames.add(JTI_CLAIM_NAME);
089                claimNames.add(EVENTS_CLAIM_NAME);
090                STD_CLAIM_NAMES = Collections.unmodifiableSet(claimNames);
091        }
092        
093        
094        /**
095         * Gets the names of the standard top-level logout token claims.
096         *
097         * @return The names of the standard top-level logout token claims
098         *         (read-only set).
099         */
100        public static Set<String> getStandardClaimNames() {
101                
102                return STD_CLAIM_NAMES;
103        }
104        
105        
106        /**
107         * Creates a new logout token claims set. Either the subject or the
108         * session ID must be set, or both.
109         *
110         * @param iss The issuer. Must not be {@code null}.
111         * @param sub The subject. Must not be {@code null} unless the session
112         *            ID is set.
113         * @param aud The audience. Must not be {@code null}.
114         * @param iat The issue time. Must not be {@code null}.
115         * @param jti The JWT ID. Must not be {@code null}.
116         * @param sid The session ID. Must not be {@code null} unless the
117         *            subject is set.
118         */
119        public LogoutTokenClaimsSet(final Issuer iss,
120                                    final Subject sub,
121                                    final List<Audience> aud,
122                                    final Date iat,
123                                    final JWTID jti,
124                                    final SessionID sid) {
125                
126                if (sub == null && sid == null) {
127                        throw new IllegalArgumentException("Either the subject or the session ID must be set, or both");
128                }
129                
130                setClaim(ISS_CLAIM_NAME, iss.getValue());
131                
132                if (sub != null) {
133                        setClaim(SUB_CLAIM_NAME, sub.getValue());
134                }
135                
136                JSONArray audList = new JSONArray();
137                
138                for (Audience a: aud)
139                        audList.add(a.getValue());
140                
141                setClaim(AUD_CLAIM_NAME, audList);
142                
143                setDateClaim(IAT_CLAIM_NAME, iat);
144                
145                setClaim(JTI_CLAIM_NAME, jti.getValue());
146                
147                JSONObject events = new JSONObject();
148                events.put(EVENT_TYPE, new JSONObject());
149                setClaim(EVENTS_CLAIM_NAME, events);
150                
151                if (sid != null) {
152                        setClaim(SID_CLAIM_NAME, sid.getValue());
153                }
154        }
155        
156        
157        /**
158         * Creates a new logout token claims set from the specified JSON
159         * object.
160         *
161         * @param jsonObject The JSON object. Must be verified to represent a
162         *                   valid logout token claims set and not be
163         *                   {@code null}.
164         *
165         * @throws ParseException If the JSON object doesn't represent a valid
166         *                        logout token claims set.
167         */
168        private LogoutTokenClaimsSet(final JSONObject jsonObject)
169                throws ParseException {
170                
171                super(jsonObject);
172                
173                if (getStringClaim(ISS_CLAIM_NAME) == null)
174                        throw new ParseException("Missing or invalid \"iss\" claim");
175                
176                if (getStringClaim(SUB_CLAIM_NAME) == null && getStringClaim(SID_CLAIM_NAME) == null)
177                        throw new ParseException("Missing or invalid \"sub\" and / or \"sid\" claim(s)");
178                
179                if (getStringClaim(AUD_CLAIM_NAME) == null && getStringListClaim(AUD_CLAIM_NAME) == null ||
180                        getStringListClaim(AUD_CLAIM_NAME) != null && getStringListClaim(AUD_CLAIM_NAME).isEmpty())
181                        throw new ParseException("Missing or invalid \"aud\" claim");
182                
183                if (getDateClaim(IAT_CLAIM_NAME) == null)
184                        throw new ParseException("Missing or invalid \"iat\" claim");
185                
186                if (getStringClaim(JTI_CLAIM_NAME) == null)
187                        throw new ParseException("Missing or invalid \"jti\" claim");
188                
189                if (getClaim(EVENTS_CLAIM_NAME) == null)
190                        throw new ParseException("Missing or invalid \"events\" claim");
191                
192                JSONObject events = getClaim(EVENTS_CLAIM_NAME, JSONObject.class);
193                
194                if (JSONObjectUtils.getJSONObject(events, EVENT_TYPE, null) == null) {
195                        throw new ParseException("Missing event type " + EVENT_TYPE);
196                }
197                
198                if (jsonObject.containsKey("nonce")) {
199                        throw new ParseException("Nonce is prohibited");
200                }
201        }
202        
203        
204        /**
205         * Creates a new logout token claims set from the specified JSON Web
206         * Token (JWT) claims set.
207         *
208         * @param jwtClaimsSet The JWT claims set. Must not be {@code null}.
209         *
210         * @throws ParseException If the JWT claims set doesn't represent a
211         *                        valid logout token claims set.
212         */
213        public LogoutTokenClaimsSet(final JWTClaimsSet jwtClaimsSet)
214                throws ParseException {
215                
216                this(jwtClaimsSet.toJSONObject());
217        }
218        
219        
220        /**
221         * Gets the JWT ID. Corresponds to the {@code jti} claim.
222         *
223         * @return The JWT ID.
224         */
225        public JWTID getJWTID() {
226                
227                return new JWTID(getStringClaim(JTI_CLAIM_NAME));
228        }
229        
230        
231        @Override
232        public JSONObject toJSONObject() {
233                
234                if (getClaim("nonce") != null) {
235                        throw new IllegalStateException("Nonce is prohibited");
236                }
237                
238                return super.toJSONObject();
239        }
240        
241        
242        @Override
243        public JWTClaimsSet toJWTClaimsSet()
244                throws ParseException {
245                
246                if (getClaim("nonce") != null) {
247                        throw new ParseException("Nonce is prohibited");
248                }
249                
250                return super.toJWTClaimsSet();
251        }
252        
253        
254        /**
255         * Parses a logout token claims set from the specified JSON object
256         * string.
257         *
258         * @param json The JSON object string to parse. Must not be
259         *             {@code null}.
260         *
261         * @return The logout token claims set.
262         *
263         * @throws ParseException If parsing failed.
264         */
265        public static LogoutTokenClaimsSet parse(final String json)
266                throws ParseException {
267                
268                JSONObject jsonObject = JSONObjectUtils.parse(json);
269                
270                try {
271                        return new LogoutTokenClaimsSet(jsonObject);
272                        
273                } catch (IllegalArgumentException e) {
274                        
275                        throw new ParseException(e.getMessage(), e);
276                }
277        }
278}