001/*- 002 * #%L 003 * Smile CDR - CDR 004 * %% 005 * Copyright (C) 2016 - 2026 Smile CDR, Inc. 006 * %% 007 * All rights reserved. 008 * #L% 009 */ 010package ca.cdr.test.app.clients; 011 012import com.fasterxml.jackson.databind.JsonNode; 013import jakarta.annotation.Nonnull; 014import jakarta.annotation.Nullable; 015 016import java.util.Objects; 017 018/** 019 * A successful OAuth 2.0 token endpoint response. 020 * 021 * @param accessToken the {@code access_token} 022 * @param tokenType the {@code token_type}, normally {@code Bearer} 023 * @param expiresIn the {@code expires_in} lifetime in seconds, if the server sent one 024 * @param scope the granted {@code scope}, if the server sent one 025 * @param idToken the OpenID Connect {@code id_token}, if one was issued 026 * @param refreshToken the {@code refresh_token}, if one was issued 027 * @param raw the whole response, for any member not listed above 028 * @see <a href="https://datatracker.ietf.org/doc/html/rfc6749#section-5.1">RFC 6749 section 5.1</a> 029 */ 030// Created by Claude Opus 5.5 031public record AccessTokenResponse( 032 @Nonnull String accessToken, 033 @Nullable String tokenType, 034 @Nullable Long expiresIn, 035 @Nullable String scope, 036 @Nullable String idToken, 037 @Nullable String refreshToken, 038 @Nonnull JsonNode raw) { 039 040 /** 041 * Reads a token endpoint response body. 042 * 043 * @param theResponse the parsed response body 044 * @return the response 045 * @throws IllegalArgumentException if the body carries no {@code access_token} 046 */ 047 public static @Nonnull AccessTokenResponse fromJson(@Nonnull JsonNode theResponse) { 048 Objects.requireNonNull(theResponse, "theResponse must not be null"); 049 String accessToken = textOrNull(theResponse, "access_token"); 050 if (accessToken == null) { 051 throw new IllegalArgumentException("access_token not found in response: " + theResponse); 052 } 053 JsonNode expiresIn = theResponse.get("expires_in"); 054 return new AccessTokenResponse( 055 accessToken, 056 textOrNull(theResponse, "token_type"), 057 expiresIn == null || expiresIn.isNull() ? null : expiresIn.asLong(), 058 textOrNull(theResponse, "scope"), 059 textOrNull(theResponse, "id_token"), 060 textOrNull(theResponse, "refresh_token"), 061 theResponse); 062 } 063 064 private static @Nullable String textOrNull(JsonNode theNode, String theField) { 065 JsonNode value = theNode.get(theField); 066 return value == null || value.isNull() ? null : value.asText(); 067 } 068}