001package com.nimbusds.openid.connect.provider.spi.grants; 002 003 004import java.util.ArrayList; 005import java.util.Date; 006import java.util.List; 007 008import com.nimbusds.oauth2.sdk.ParseException; 009import com.nimbusds.oauth2.sdk.Scope; 010import com.nimbusds.oauth2.sdk.id.Subject; 011import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 012import com.nimbusds.openid.connect.sdk.claims.ACR; 013import com.nimbusds.openid.connect.sdk.claims.AMR; 014import net.jcip.annotations.Immutable; 015import net.minidev.json.JSONObject; 016 017 018/** 019 * OAuth 2.0 / OpenID Connect authorisation produced by a {@link GrantHandler} 020 * specifying a subject (end-user) and permitting ID token issue. 021 * 022 * <p>Required authorisation details: 023 * 024 * <ul> 025 * <li>The subject (end-user). 026 * <li>The authorised scope. 027 * </ul> 028 * 029 * <p>All other parameters are optional or have suitable defaults. 030 */ 031@Immutable 032public class SubjectAuthorization extends GrantAuthorization { 033 034 035 /** 036 * The identifier of the authorised subject. 037 */ 038 private final Subject subject; 039 040 041 /** 042 * The ID token specification. 043 */ 044 private final IDTokenSpec idTokenSpec; 045 046 047 /** 048 * The OpenID Connect claims spec. 049 */ 050 private final ClaimsSpec claimsSpec; 051 052 053 /** 054 * Creates a new authorisation for the specified subject. 055 * 056 * @param subject The subject (end-user) identifier. Must not 057 * be {@code null}. 058 * @param scope The authorised scope values. Must not be 059 * {@code null}. 060 * @param accessTokenSpec The access token specification. Must not be 061 * {@code null}. 062 * @param idTokenSpec The ID token specification. Must not be 063 * {@code null}. 064 * @param claimsSpec The claims specification. Must not be 065 * {@code null}. 066 * @param data Additional data as a JSON object, 067 * {@code null} if not specified. 068 */ 069 public SubjectAuthorization(final Subject subject, 070 final Scope scope, 071 final AccessTokenSpec accessTokenSpec, 072 final IDTokenSpec idTokenSpec, 073 final ClaimsSpec claimsSpec, 074 final JSONObject data) { 075 076 super(scope, accessTokenSpec, data); 077 078 if (subject == null) { 079 throw new IllegalArgumentException("The subject must not be null"); 080 } 081 082 this.subject = subject; 083 084 if (idTokenSpec == null) { 085 throw new IllegalArgumentException("The ID token specification must not be null"); 086 } 087 088 this.idTokenSpec = idTokenSpec; 089 090 if (claimsSpec == null) { 091 throw new IllegalArgumentException("The claim specification must not be null"); 092 } 093 094 this.claimsSpec = claimsSpec; 095 } 096 097 098 /** 099 * Returns the subject (end-user) identifier. 100 * 101 * @return The subject identifier. 102 */ 103 public Subject getSubject() { 104 105 return subject; 106 } 107 108 109 /** 110 * Returns the time of the subject authentication. 111 * 112 * @return The time of the subject authentication. If {@code null} it 113 * will be set to now. Applies only if an ID token is issued. 114 */ 115 public Date getAuthTime() { 116 117 return getIDTokenSpec().getAuthTime(); 118 } 119 120 121 /** 122 * Returns the Authentication Context Class Reference (ACR). 123 * 124 * @return The Authentication Context Class Reference (ACR), 125 * {@code null} if not specified. Applies only if an ID token 126 * is issued. 127 */ 128 public ACR getACR() { 129 130 return getIDTokenSpec().getACR(); 131 } 132 133 134 /** 135 * Returns The Authentication Methods Reference (AMR) list. 136 * 137 * @return The Authentication Methods Reference (AMR) list, 138 * {@code null} if not specified. Applies only if an ID token 139 * is issued. 140 */ 141 public List<AMR> getAMRList() { 142 143 return getIDTokenSpec().getAMRList(); 144 } 145 146 147 /** 148 * Returns the ID token specification. 149 * 150 * @return The ID token specification. 151 */ 152 public IDTokenSpec getIDTokenSpec() { 153 154 return idTokenSpec; 155 } 156 157 158 /** 159 * Returns the claims specification. 160 * 161 * @return The claims specification. 162 */ 163 public ClaimsSpec getClaimsSpec() { 164 165 return claimsSpec; 166 } 167 168 169 @Override 170 public JSONObject toJSONObject() { 171 172 JSONObject o = super.toJSONObject(); 173 174 o.put("sub", subject.getValue()); 175 176 if (idTokenSpec.issue()) { 177 JSONObject idTokenJSONObject = idTokenSpec.toJSONObject(); 178 // Move auth_time, acr and amr to top level 179 if (idTokenJSONObject.get("auth_time") != null) { 180 o.put("auth_time", idTokenJSONObject.remove("auth_time")); 181 } 182 if (idTokenJSONObject.get("acr") != null) { 183 o.put("acr", idTokenJSONObject.remove("acr")); 184 } 185 if (idTokenJSONObject.get("amr") != null) { 186 o.put("amr", idTokenJSONObject.remove("amr")); 187 } 188 o.put("id_token", idTokenJSONObject); 189 } 190 191 o.putAll(claimsSpec.toJSONObject()); 192 193 return o; 194 } 195 196 197 /** 198 * Parses a subject authorisation from the specified JSON object. 199 * 200 * @param jsonObject The JSON object to parse. Must not be {@code null}. 201 * 202 * @return The subject authorisation. 203 * 204 * @throws ParseException If parsing failed. 205 */ 206 public static SubjectAuthorization parse(final JSONObject jsonObject) 207 throws ParseException { 208 209 GrantAuthorization basicAuthz = GrantAuthorization.parse(jsonObject); 210 211 Subject sub = new Subject(JSONObjectUtils.getString(jsonObject, "sub")); 212 213 // Backward API compatibility 214 Date topLevelAuthTime = null; 215 216 if (jsonObject.containsKey("auth_time")) { 217 topLevelAuthTime = new Date(JSONObjectUtils.getLong(jsonObject, "auth_time") * 1000L); 218 } 219 220 ACR topLevelACR = null; 221 222 if (jsonObject.containsKey("acr")) { 223 topLevelACR = new ACR(JSONObjectUtils.getString(jsonObject, "acr")); 224 } 225 226 List<AMR> topLevelAMRList = null; 227 228 if (jsonObject.containsKey("amr")) { 229 String[] sa = JSONObjectUtils.getStringArray(jsonObject, "amr"); 230 topLevelAMRList = new ArrayList<>(sa.length); 231 for (String s: sa) { 232 topLevelAMRList.add(new AMR(s)); 233 } 234 } 235 236 IDTokenSpec idSpec = new IDTokenSpec(); 237 238 if (jsonObject.containsKey("id_token")) { 239 240 idSpec = IDTokenSpec.parse(JSONObjectUtils.getJSONObject(jsonObject, "id_token")); 241 242 // Override with top-level params where set 243 idSpec = new IDTokenSpec( 244 idSpec.issue(), 245 idSpec.getLifetime(), 246 topLevelAuthTime != null ? topLevelAuthTime : idSpec.getAuthTime(), 247 topLevelACR != null ? topLevelACR : idSpec.getACR(), 248 topLevelAMRList != null ? topLevelAMRList : idSpec.getAMRList(), 249 idSpec.getImpersonatedSubject()); 250 } 251 252 ClaimsSpec clSpec = ClaimsSpec.parse(jsonObject); 253 254 return new SubjectAuthorization( 255 sub, 256 basicAuthz.getScope(), 257 basicAuthz.getAccessTokenSpec(), 258 idSpec, 259 clSpec, 260 basicAuthz.getData()); 261 } 262 263 264 /** 265 * Parses a subject authorisation from the specified JSON object 266 * string. 267 * 268 * @param json The JSON object string to parse. Must not be 269 * {@code null}. 270 * 271 * @return The subject authorisation. 272 * 273 * @throws ParseException If parsing failed. 274 */ 275 public static SubjectAuthorization parse(final String json) 276 throws ParseException { 277 278 return parse(JSONObjectUtils.parse(json)); 279 } 280}