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 &mdash; 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}