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 07). 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 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(JSONObjectUtils.toJSONObject(jwtClaimsSet)); 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}