// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.microsoft.aad.msal4j; import com.azure.json.JsonProviders; import com.azure.json.JsonReader; import com.azure.json.JsonSerializable; import com.azure.json.JsonToken; import com.azure.json.JsonWriter; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; /** * Represents the claims request parameter as an object * * @see https://openid.net/specs/openid-connect-core-1_0-final.html#ClaimsParameter */ public class ClaimsRequest implements JsonSerializable { List idTokenRequestedClaims = new ArrayList<>(); List userInfoRequestedClaims = new ArrayList<>(); List accessTokenRequestedClaims = new ArrayList<>(); /** * Inserts a claim into the list of claims to be added to the "id_token" section of an OIDC claims request * * @param claim the name of the claim to be requested * @param requestedClaimAdditionalInfo additional information about the claim being requested */ public void requestClaimInIdToken(String claim, RequestedClaimAdditionalInfo requestedClaimAdditionalInfo) { idTokenRequestedClaims.add(new RequestedClaim(claim, requestedClaimAdditionalInfo)); } /** * Inserts a claim into the list of claims to be added to the "userinfo" section of an OIDC claims request * * @param claim the name of the claim to be requested * @param requestedClaimAdditionalInfo additional information about the claim being requested */ protected void requestClaimInUserInfo(String claim, RequestedClaimAdditionalInfo requestedClaimAdditionalInfo) { userInfoRequestedClaims.add(new RequestedClaim(claim, requestedClaimAdditionalInfo)); } /** * Inserts a claim into the list of claims to be added to the "access_token" section of an OIDC claims request * * @param claim the name of the claim to be requested * @param requestedClaimAdditionalInfo additional information about the claim being requested */ protected void requestClaimInAccessToken(String claim, RequestedClaimAdditionalInfo requestedClaimAdditionalInfo) { accessTokenRequestedClaims.add(new RequestedClaim(claim, requestedClaimAdditionalInfo)); } /** * Converts the ClaimsRequest object to a JSON-formatted String which follows the specification for the OIDC claims request parameter * * @return a String following JSON formatting */ public String formatAsJSONString() { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); JsonWriter jsonWriter = JsonProviders.createWriter(outputStream)) { toJson(jsonWriter); jsonWriter.flush(); return outputStream.toString(StandardCharsets.UTF_8.name()); } catch (IOException e) { throw new MsalClientException("Could not convert ClaimsRequest to string: " + e.getMessage(), AuthenticationErrorCode.INVALID_JSON); } } @Override public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { jsonWriter.writeStartObject(); writeClaimsToJsonWriter(jsonWriter, "id_token", idTokenRequestedClaims); writeClaimsToJsonWriter(jsonWriter, "userinfo", userInfoRequestedClaims); writeClaimsToJsonWriter(jsonWriter, "access_token", accessTokenRequestedClaims); jsonWriter.writeEndObject(); return jsonWriter; } private void writeClaimsToJsonWriter(JsonWriter jsonWriter, String sectionName, List claims) throws IOException { if (claims.isEmpty()) { return; } jsonWriter.writeStartObject(sectionName); for (RequestedClaim claim : claims) { if (claim.name != null) { if (claim.getRequestedClaimAdditionalInfo() != null) { jsonWriter.writeJsonField(claim.name, claim.getRequestedClaimAdditionalInfo()); } else { jsonWriter.writeNullField(claim.name); } } } jsonWriter.writeEndObject(); } /** * Creates an instance of ClaimsRequest from a JSON-formatted String which follows the specification for the OIDC claims request parameter * * @param claims a String following JSON formatting * @return a ClaimsRequest instance */ public static ClaimsRequest formatAsClaimsRequest(String claims) { try (JsonReader jsonReader = JsonProviders.createReader(claims)) { ClaimsRequest claimsRequest = new ClaimsRequest(); return jsonReader.readObject(reader -> { if (reader.currentToken() != JsonToken.START_OBJECT) { throw new IllegalStateException("Expected start of object but was " + reader.currentToken()); } while (reader.nextToken() != JsonToken.END_OBJECT) { parseClaims(reader, claimsRequest, reader.getFieldName()); } return claimsRequest; }); } catch (IOException e) { throw new MsalClientException("Could not convert string to ClaimsRequest: " + e.getMessage(), AuthenticationErrorCode.INVALID_JSON); } } private static void parseClaims(JsonReader jsonReader, ClaimsRequest claimsRequest, String section) throws IOException { if (jsonReader.currentToken() != JsonToken.FIELD_NAME) { jsonReader.nextToken(); } jsonReader.nextToken(); if (jsonReader.currentToken() == JsonToken.NULL) { return; } if (jsonReader.currentToken() != JsonToken.START_OBJECT) { throw new IllegalStateException("Expected start of object but was " + jsonReader.currentToken()); } while (jsonReader.nextToken() != JsonToken.END_OBJECT) { String claimName = jsonReader.getFieldName(); jsonReader.nextToken(); RequestedClaimAdditionalInfo claimInfo = null; if (jsonReader.currentToken() == JsonToken.START_OBJECT) { boolean essential = false; String value = null; List values = null; while (jsonReader.nextToken() != JsonToken.END_OBJECT) { String fieldName = jsonReader.getFieldName(); jsonReader.nextToken(); switch (fieldName) { case "essential": essential = jsonReader.getBoolean(); break; case "value": value = jsonReader.getString(); break; case "values": values = new ArrayList<>(); if (jsonReader.currentToken() == JsonToken.START_ARRAY) { while (jsonReader.nextToken() != JsonToken.END_ARRAY) { values.add(jsonReader.getString()); } } break; default: jsonReader.skipChildren(); break; } } if (essential || value != null || values != null) { claimInfo = new RequestedClaimAdditionalInfo(essential, value, values); } } switch (section) { case "access_token": claimsRequest.requestClaimInAccessToken(claimName, claimInfo); break; case "id_token": claimsRequest.requestClaimInIdToken(claimName, claimInfo); break; case "userinfo": claimsRequest.requestClaimInUserInfo(claimName, claimInfo); break; } } } public List getIdTokenRequestedClaims() { return this.idTokenRequestedClaims; } public void setIdTokenRequestedClaims(List idTokenRequestedClaims) { this.idTokenRequestedClaims = idTokenRequestedClaims; } }