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 com.nimbusds.jwt.JWTClaimsSet;
022import com.nimbusds.oauth2.sdk.ParseException;
023import com.nimbusds.oauth2.sdk.id.Audience;
024import com.nimbusds.oauth2.sdk.id.Issuer;
025import com.nimbusds.oauth2.sdk.id.JWTID;
026import com.nimbusds.oauth2.sdk.id.Subject;
027import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
028import net.minidev.json.JSONArray;
029import net.minidev.json.JSONObject;
030
031import java.util.*;
032
033
034/**
035 * Back-channel logout token claims set, serialisable to a JSON object.
036 *
037 * <p>Example logout token claims set:
038 *
039 * <pre>
040 * {
041 *   "iss"    : "https://server.example.com",
042 *   "sub"    : "248289761001",
043 *   "aud"    : "s6BhdRkqt3",
044 *   "iat"    : 1471566154,
045 *   "exp"    : 1471569754,
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
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 logout 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 exp The expiration time. Must not be {@code null}.
116         * @param jti The JWT ID. Must not be {@code null}.
117         * @param sid The session ID. Must not be {@code null} unless the
118         *            subject is set.
119         */
120        public LogoutTokenClaimsSet(final Issuer iss,
121                                    final Subject sub,
122                                    final List<Audience> aud,
123                                    final Date iat,
124                                    final Date exp,
125                                    final JWTID jti,
126                                    final SessionID sid) {
127                
128                if (sub == null && sid == null) {
129                        throw new IllegalArgumentException("Either the subject or the session ID must be set, or both");
130                }
131                
132                setClaim(ISS_CLAIM_NAME, iss.getValue());
133                
134                if (sub != null) {
135                        setClaim(SUB_CLAIM_NAME, sub.getValue());
136                }
137                
138                JSONArray audList = new JSONArray();
139                
140                for (Audience a: aud)
141                        audList.add(a.getValue());
142                
143                setClaim(AUD_CLAIM_NAME, audList);
144                
145                setDateClaim(IAT_CLAIM_NAME, iat);
146
147                setDateClaim(EXP_CLAIM_NAME, exp);
148                
149                setClaim(JTI_CLAIM_NAME, jti.getValue());
150                
151                JSONObject events = new JSONObject();
152                events.put(EVENT_TYPE, new JSONObject());
153                setClaim(EVENTS_CLAIM_NAME, events);
154                
155                if (sid != null) {
156                        setClaim(SID_CLAIM_NAME, sid.getValue());
157                }
158        }
159
160
161        /**
162         * Creates a new logout token claims set. Either the subject or the
163         * session ID must be set, or both.
164         *
165         * @param iss The issuer. Must not be {@code null}.
166         * @param sub The subject. Must not be {@code null} unless the session
167         *            ID is set.
168         * @param aud The audience. Must not be {@code null}.
169         * @param iat The issue time. Must not be {@code null}.
170         * @param jti The JWT ID. Must not be {@code null}.
171         * @param sid The session ID. Must not be {@code null} unless the
172         *            subject is set.
173         */
174        @Deprecated
175        public LogoutTokenClaimsSet(final Issuer iss,
176                                    final Subject sub,
177                                    final List<Audience> aud,
178                                    final Date iat,
179                                    final JWTID jti,
180                                    final SessionID sid) {
181
182                this(iss, sub, aud, iat, null, jti, sid);
183        }
184        
185        
186        /**
187         * Creates a new logout token claims set from the specified JSON
188         * object.
189         *
190         * @param jsonObject The JSON object. Must be verified to represent a
191         *                   valid logout token claims set and not be
192         *                   {@code null}.
193         *
194         * @throws ParseException If the JSON object doesn't represent a valid
195         *                        logout token claims set.
196         */
197        private LogoutTokenClaimsSet(final JSONObject jsonObject)
198                throws ParseException {
199                
200                super(jsonObject);
201                
202                if (getStringClaim(ISS_CLAIM_NAME) == null)
203                        throw new ParseException("Missing or invalid iss claim");
204                
205                if (getStringClaim(SUB_CLAIM_NAME) == null && getStringClaim(SID_CLAIM_NAME) == null)
206                        throw new ParseException("Missing or invalid sub and / or sid claim(s)");
207                
208                if (getStringClaim(AUD_CLAIM_NAME) == null && getStringListClaim(AUD_CLAIM_NAME) == null ||
209                        getStringListClaim(AUD_CLAIM_NAME) != null && getStringListClaim(AUD_CLAIM_NAME).isEmpty())
210                        throw new ParseException("Missing or invalid aud claim");
211                
212                if (getDateClaim(IAT_CLAIM_NAME) == null)
213                        throw new ParseException("Missing or invalid iat claim");
214
215                if (getDateClaim(EXP_CLAIM_NAME) == null)
216                        throw new ParseException("Missing or invalid exp claim");
217                
218                if (getStringClaim(JTI_CLAIM_NAME) == null)
219                        throw new ParseException("Missing or invalid jti claim");
220                
221                if (getClaim(EVENTS_CLAIM_NAME) == null)
222                        throw new ParseException("Missing or invalid events claim");
223                
224                JSONObject events = getClaim(EVENTS_CLAIM_NAME, JSONObject.class);
225                
226                if (JSONObjectUtils.getJSONObject(events, EVENT_TYPE, null) == null) {
227                        throw new ParseException("Missing event type " + EVENT_TYPE);
228                }
229                
230                if (jsonObject.containsKey("nonce")) {
231                        throw new ParseException("Nonce is prohibited");
232                }
233        }
234        
235        
236        /**
237         * Creates a new logout token claims set from the specified JSON Web
238         * Token (JWT) claims set.
239         *
240         * @param jwtClaimsSet The JWT claims set. Must not be {@code null}.
241         *
242         * @throws ParseException If the JWT claims set doesn't represent a
243         *                        valid logout token claims set.
244         */
245        public LogoutTokenClaimsSet(final JWTClaimsSet jwtClaimsSet)
246                throws ParseException {
247                
248                this(JSONObjectUtils.toJSONObject(jwtClaimsSet));
249        }
250        
251        
252        /**
253         * Gets the JWT ID. Corresponds to the {@code jti} claim.
254         *
255         * @return The JWT ID.
256         */
257        public JWTID getJWTID() {
258                
259                return new JWTID(getStringClaim(JTI_CLAIM_NAME));
260        }
261        
262        
263        @Override
264        public JSONObject toJSONObject() {
265                
266                if (getClaim("nonce") != null) {
267                        throw new IllegalStateException("Nonce is prohibited");
268                }
269                
270                return super.toJSONObject();
271        }
272        
273        
274        @Override
275        public JWTClaimsSet toJWTClaimsSet()
276                throws ParseException {
277                
278                if (getClaim("nonce") != null) {
279                        throw new ParseException("Nonce is prohibited");
280                }
281                
282                return super.toJWTClaimsSet();
283        }
284        
285        
286        /**
287         * Parses a logout token claims set from the specified JSON object
288         * string.
289         *
290         * @param json The JSON object string to parse. Must not be
291         *             {@code null}.
292         *
293         * @return The logout token claims set.
294         *
295         * @throws ParseException If parsing failed.
296         */
297        public static LogoutTokenClaimsSet parse(final String json)
298                throws ParseException {
299                
300                JSONObject jsonObject = JSONObjectUtils.parse(json);
301                
302                try {
303                        return new LogoutTokenClaimsSet(jsonObject);
304                        
305                } catch (IllegalArgumentException e) {
306                        
307                        throw new ParseException(e.getMessage(), e);
308                }
309        }
310}