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}