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 * </ul>
056 */
057public class LogoutTokenClaimsSet extends CommonClaimsSet {
058        
059        
060        /**
061         * The JWT ID claim name.
062         */
063        public static final String JTI_CLAIM_NAME = "jti";
064        
065        
066        /**
067         * The events claim name.
068         */
069        public static final String EVENTS_CLAIM_NAME = "events";
070        
071        
072        /**
073         * The OpenID logout event type.
074         */
075        public static final String EVENT_TYPE = "http://schemas.openid.net/event/backchannel-logout";
076        
077        
078        /**
079         * The names of the standard top-level ID token claims.
080         */
081        private static final Set<String> stdClaimNames = new LinkedHashSet<>();
082        
083        
084        static {
085                stdClaimNames.add(ISS_CLAIM_NAME);
086                stdClaimNames.add(SUB_CLAIM_NAME);
087                stdClaimNames.add(AUD_CLAIM_NAME);
088                stdClaimNames.add(IAT_CLAIM_NAME);
089                stdClaimNames.add(JTI_CLAIM_NAME);
090                stdClaimNames.add(EVENTS_CLAIM_NAME);
091                stdClaimNames.add(SID_CLAIM_NAME);
092        }
093        
094        
095        /**
096         * Gets the names of the standard top-level logout token claims.
097         *
098         * @return The names of the standard top-level logout token claims
099         *         (read-only set).
100         */
101        public static Set<String> getStandardClaimNames() {
102                
103                return Collections.unmodifiableSet(stdClaimNames);
104        }
105        
106        
107        /**
108         * Creates a new logout token claims set. Either the subject or the
109         * session ID must be set, or both.
110         *
111         * @param iss The issuer. Must not be {@code null}.
112         * @param sub The subject. Must not be {@code null} unless the session
113         *            ID is set.
114         * @param aud The audience. Must not be {@code null}.
115         * @param iat The issue 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 JWTID jti,
125                                    final SessionID sid) {
126                
127                if (sub == null && sid == null) {
128                        throw new IllegalArgumentException("Either the subject or the session ID must be set, or both");
129                }
130                
131                setClaim(ISS_CLAIM_NAME, iss.getValue());
132                
133                if (sub != null) {
134                        setClaim(SUB_CLAIM_NAME, sub.getValue());
135                }
136                
137                JSONArray audList = new JSONArray();
138                
139                for (Audience a: aud)
140                        audList.add(a.getValue());
141                
142                setClaim(AUD_CLAIM_NAME, audList);
143                
144                setDateClaim(IAT_CLAIM_NAME, iat);
145                
146                setClaim(JTI_CLAIM_NAME, jti.getValue());
147                
148                JSONObject events = new JSONObject();
149                events.put(EVENT_TYPE, new JSONObject());
150                setClaim(EVENTS_CLAIM_NAME, events);
151                
152                if (sid != null) {
153                        setClaim(SID_CLAIM_NAME, sid.getValue());
154                }
155        }
156        
157        
158        /**
159         * Creates a new logout token claims set from the specified JSON
160         * object.
161         *
162         * @param jsonObject The JSON object. Must be verified to represent a
163         *                   valid logout token claims set and not be
164         *                   {@code null}.
165         *
166         * @throws ParseException If the JSON object doesn't represent a valid
167         *                        logout token claims set.
168         */
169        private LogoutTokenClaimsSet(final JSONObject jsonObject)
170                throws ParseException {
171                
172                super(jsonObject);
173                
174                if (getStringClaim(ISS_CLAIM_NAME) == null)
175                        throw new ParseException("Missing or invalid \"iss\" claim");
176                
177                if (getStringClaim(SUB_CLAIM_NAME) == null && getStringClaim(SID_CLAIM_NAME) == null)
178                        throw new ParseException("Missing or invalid \"sub\" and / or \"sid\" claim(s)");
179                
180                if (getStringClaim(AUD_CLAIM_NAME) == null && getStringListClaim(AUD_CLAIM_NAME) == null ||
181                        getStringListClaim(AUD_CLAIM_NAME) != null && getStringListClaim(AUD_CLAIM_NAME).isEmpty())
182                        throw new ParseException("Missing or invalid \"aud\" claim");
183                
184                if (getDateClaim(IAT_CLAIM_NAME) == null)
185                        throw new ParseException("Missing or invalid \"iat\" claim");
186                
187                if (getStringClaim(JTI_CLAIM_NAME) == null)
188                        throw new ParseException("Missing or invalid \"jti\" claim");
189                
190                if (getClaim(EVENTS_CLAIM_NAME) == null)
191                        throw new ParseException("Missing or invalid \"events\" claim");
192                
193                JSONObject events = getClaim(EVENTS_CLAIM_NAME, JSONObject.class);
194                
195                if (JSONObjectUtils.getJSONObject(events, EVENT_TYPE) == null) {
196                        throw new ParseException("Missing event type " + EVENT_TYPE);
197                }
198                
199                if (jsonObject.containsKey("nonce")) {
200                        throw new ParseException("Nonce is prohibited");
201                }
202        }
203        
204        
205        /**
206         * Creates a new logout token claims set from the specified JSON Web
207         * Token (JWT) claims set.
208         *
209         * @param jwtClaimsSet The JWT claims set. Must not be {@code null}.
210         *
211         * @throws ParseException If the JWT claims set doesn't represent a
212         *                        valid logout token claims set.
213         */
214        public LogoutTokenClaimsSet(final JWTClaimsSet jwtClaimsSet)
215                throws ParseException {
216                
217                this(jwtClaimsSet.toJSONObject());
218        }
219        
220        
221        /**
222         * Gets the JWT ID. Corresponds to the {@code jti} claim.
223         *
224         * @return The JWT ID.
225         */
226        public JWTID getJWTID() {
227                
228                return new JWTID(getStringClaim(JTI_CLAIM_NAME));
229        }
230        
231        
232        @Override
233        public JSONObject toJSONObject() {
234                
235                if (getClaim("nonce") != null) {
236                        throw new IllegalStateException("Nonce is prohibited");
237                }
238                
239                return super.toJSONObject();
240        }
241        
242        
243        @Override
244        public JWTClaimsSet toJWTClaimsSet()
245                throws ParseException {
246                
247                if (getClaim("nonce") != null) {
248                        throw new ParseException("Nonce is prohibited");
249                }
250                
251                return super.toJWTClaimsSet();
252        }
253        
254        
255        /**
256         * Parses a logout token claims set from the specified JSON object
257         * string.
258         *
259         * @param json The JSON object string to parse. Must not be
260         *             {@code null}.
261         *
262         * @return The logout token claims set.
263         *
264         * @throws ParseException If parsing failed.
265         */
266        public static LogoutTokenClaimsSet parse(final String json)
267                throws ParseException {
268                
269                JSONObject jsonObject = JSONObjectUtils.parse(json);
270                
271                try {
272                        return new LogoutTokenClaimsSet(jsonObject);
273                        
274                } catch (IllegalArgumentException e) {
275                        
276                        throw new ParseException(e.getMessage(), e);
277                }
278        }
279}