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