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}