001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.client.mcp.auth; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021 022/** 023 * Bridges a Dynamic-Client-Registration result ({@link McpClientRegistration}) into the existing token flows — 024 * the {@link McpAuthorizationCodeAcquirer} (interactive auth-code + PKCE) and {@link McpTokenProvider} 025 * (client-credentials / refresh). 026 * 027 * <p> 028 * DCR changes only how the {@code client_id}/secret were obtained; it does <b>not</b> alter the {@code resource=} 029 * (RFC 8707) or {@code iss} (RFC 9207 / SEP-2468) enforcement those flows already perform — the caller still sets 030 * {@code resource(...)} / {@code expectedIssuer(...)} exactly as for a statically-configured client. These helpers only 031 * transplant the DCR-issued credential material (and, for the acquirer, keep the loopback redirect <em>path and port</em> 032 * consistent with what was registered, so a bind-first {@link LoopbackRedirectUris#forPort forPort} registration matches 033 * at a strict exact-match authorization server). 034 * 035 * @since 10.0.0 036 */ 037public final class McpClientRegistrations { 038 039 private McpClientRegistrations() {} 040 041 /** 042 * Applies a DCR-issued registration's {@code client_id}, optional {@code client_secret}, and registered loopback 043 * redirect path (and port, for a bind-first registration) onto an {@link McpAuthorizationCodeAcquirer.Builder}. 044 * 045 * <p> 046 * When the registration's first redirect URI carries an explicit port (a bind-first 047 * {@link LoopbackRedirectUris#forPort forPort} registration), that port is wired onto the builder via 048 * {@link McpAuthorizationCodeAcquirer.Builder#redirectPort(int) redirectPort} so the loopback receiver binds exactly 049 * the port the authorization server was told; a port-agnostic registration leaves the acquirer on its ephemeral-port 050 * default. 051 * 052 * @param builder The acquirer builder. Must not be <jk>null</jk>. 053 * @param registration The DCR-issued registration. Must not be <jk>null</jk>. 054 * @return The same builder, for chaining. 055 */ 056 public static McpAuthorizationCodeAcquirer.Builder configure(McpAuthorizationCodeAcquirer.Builder builder, McpClientRegistration registration) { 057 reqnn("builder", builder); 058 reqnn("registration", registration); 059 builder.clientId(registration.clientId()); 060 registration.clientSecret().ifPresent(builder::clientSecret); 061 // Keep the loopback receiver's callback path AND port consistent with the redirect URI that was actually 062 // registered: a strict exact-match AS matches the redirect URI exactly, so a bind-first forPort(port,path) 063 // registration only works if the receiver binds the same path and the same port. 064 if (! registration.redirectUris().isEmpty()) { 065 var redirect = registration.redirectUris().get(0); 066 var path = redirect.getPath(); 067 if (path != null && path.startsWith("/")) 068 builder.redirectPath(path); 069 var port = redirect.getPort(); 070 if (port > 0) 071 builder.redirectPort(port); 072 } 073 return builder; 074 } 075 076 /** 077 * Applies a DCR-issued registration's {@code client_id} and optional {@code client_secret} onto an 078 * {@link McpTokenProvider.Builder} (client-credentials / refresh modes). 079 * 080 * @param builder The token-provider builder. Must not be <jk>null</jk>. 081 * @param registration The DCR-issued registration. Must not be <jk>null</jk>. 082 * @return The same builder, for chaining. 083 */ 084 public static McpTokenProvider.Builder configure(McpTokenProvider.Builder builder, McpClientRegistration registration) { 085 reqnn("builder", builder); 086 reqnn("registration", registration); 087 builder.clientId(registration.clientId()); 088 registration.clientSecret().ifPresent(builder::clientSecret); 089 return builder; 090 } 091}