From b037c11b4dcfeb04b95e7ecae54222719f4174df Mon Sep 17 00:00:00 2001 From: maximthomas Date: Wed, 5 Nov 2025 10:17:17 +0300 Subject: [PATCH 01/14] Add OpenAM MCP Server to the OpenAM Tools list --- openam-tools/openam-mcp-server/README.md | 267 ++++++++++++++++++ openam-tools/openam-mcp-server/pom.xml | 86 ++++++ .../server/OpenAmMcpServerApplication.java | 33 +++ .../mcp/server/config/OpenAMConfig.java | 17 ++ .../openam/mcp/server/config/WebConfig.java | 22 ++ .../server/controller/OAuth2Controller.java | 49 ++++ .../openam/mcp/server/model/User.java | 27 ++ .../openam/mcp/server/model/UserDTO.java | 30 ++ .../mcp/server/model/UserSearchResponse.java | 15 + .../mcp/server/security/AuthInterceptor.java | 164 +++++++++++ .../mcp/server/service/UserService.java | 169 +++++++++++ .../src/main/resources/application.yml | 26 ++ .../OpenAmMcpServerApplicationTests.java | 12 + .../mcp/server/service/UserServiceTest.java | 125 ++++++++ .../test/resources/users/user-response.json | 76 +++++ .../resources/users/users-list-response.json | 119 ++++++++ openam-tools/pom.xml | 21 ++ 17 files changed, 1258 insertions(+) create mode 100644 openam-tools/openam-mcp-server/README.md create mode 100644 openam-tools/openam-mcp-server/pom.xml create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplication.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/OpenAMConfig.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/WebConfig.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/User.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserDTO.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserSearchResponse.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java create mode 100644 openam-tools/openam-mcp-server/src/main/resources/application.yml create mode 100644 openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplicationTests.java create mode 100644 openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java create mode 100644 openam-tools/openam-mcp-server/src/test/resources/users/user-response.json create mode 100644 openam-tools/openam-mcp-server/src/test/resources/users/users-list-response.json diff --git a/openam-tools/openam-mcp-server/README.md b/openam-tools/openam-mcp-server/README.md new file mode 100644 index 0000000000..301daa000d --- /dev/null +++ b/openam-tools/openam-mcp-server/README.md @@ -0,0 +1,267 @@ +# OpenAM MCP Server + +OpenAM MCP Server is a lightweight management service for OpenAM user accounts. +It allows administrators to create, update, delete, and reset passwords for users. + +## Prerequisites +* JDK 17+ +* [OpenAM](http://github.com/OpenIdentityPlatform/OpenAM) installed + +## Quick Start +Set the `OPENAM_URL` environment variable, i.e., http://openam.example.org:8080/openam +Set the `OPENAM_ADMIN_USERNAME` (i.e., `amadmin`) and the `OPENAM_ADMIN_PASSWORD` environment variables (i.e., `passw0rd`). + +```bash +export OPENAM_URL=http://openam.example.org:8080/openam +export OPENAM_ADMIN_USERNAME=amadmin +export OPENAM_ADMIN_PASSWORD=passw0rd +``` + +Clone the source code: + +Run from the source code: + +```bash +mvn spring-boot:run +``` +Build and run: + +```bash +cd openam-mcp-server +mvn package -DskipTests=true && java -jar ./target/openam-mcp-server-*.jar +``` + +## Advanced Authentication + +> [!IMPORTANT] +> Using administrative credentials directly in the MCP server may be insecure, so this server supports OpenAM's OAuth 2.0 protocol. +protocol. + +This approach requires additional OpenAM configuration: + +### OpenAM OAuth2.0 Service Configuration + +Go to the OpenAM admin console, select the root realm, then select **Configure OAuth Provider** → **Configure OAuth2.0**. +Leave the settings unchanged and click the Create button. + +Set the following settings for the OAuth2.0 Provider: + +| Setting | Value | +|------------------------------------------------------------------|---------| +| Use Stateless Access & Refresh Tokens | enabled | +| User Profile Attribute(s) the Resource Owner is Authenticated On | uid | +| Supported Scopes | profile | +| OAuth2 Token Signing Algorithm | RS256 | +| Allow Open Dynamic Client Registration | enabled | + +See more in the OpenAM OAuth2.0 documentation: https://doc.openidentityplatform.org/openam/admin-guide/chap-oauth2 + +### Authentication Chain Configuration +Create the OpenAM OAuth 2.0 authentication chain, so that the MCP server can exchange an access token for an SSO token to manage identities. +In the administrator console, select the root realm. Then in the left menu select **Authentication** → **Modules** and create a new module with name `oidc` and type `OpenID Connect id_token bearer`. + +Set the following settings for the `oidc` authentication module: + +| Setting | Value | +|----------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------| +| OpenID Connect validation configuration value | OpenAM's .well-known/openid-configuration endpoint, i.e., http://openam.example.org:8080/openam/oauth2/.well-known/openid-configuration | +| Name of OpenID Connect ID Token Issuer | http://openam.example.org:8080/openam/oauth2 | +| Mapping of jwt attributes to local LDAP attributes | sub=uid | +| Audience name | Your MCP client's client ID, for example openam-mcp-server | + +Create an authentication chain with the `oidc` module: + +In the administrator console select the root realm then in the left menu select **Authentication** → **Chains** and create a new chain with name `oidc` and the following settings: + +| Module | Criteria | +|--------|------------| +| oidc | REQUISITE | + +Set the MCP server environment variable `OPENAM_USE_OAUTH` to `true` + + +## Available MCP Server Tools + +```java +@Tool(name = "get_users", description = "Returns OpenAM user list from the default (root) realm") +public List getUsers(@ToolParam(required = false, description = "If not set, uses root realm") String realm, + @ToolParam(required = false, description = "Username filter") String filter) + +@Tool(name = "set_user_attribute", description = "Sets the attribute value for a user") +public User setUserAttribute(@ToolParam(required = false, description = "If not set, uses root realm") String realm, + @ToolParam(description = "username") String username, + @ToolParam(description = "user attribute name") String attribute, + @ToolParam(description = "user attribute value") String value) + +@Tool(name = "set_user_password", description = "Sets the password for a user") +public User setUserPassword(@ToolParam(required = false, description = "If not set, uses root realm") String realm, + @ToolParam(description = "username") String username, + @ToolParam(description = "user password") String password) + +@Tool(name = "create_user", description = "Creates a new user") +public User createUser(@ToolParam(required = false, description = "If not set, uses root realm") String realm, + @ToolParam(description = "Username (login)") String userName, + @ToolParam(description = "Password (min length 8)") String password, + @ToolParam(required = false, description = "User family name") String familyName, + @ToolParam(required = false, description = "User given name") String givenName, + @ToolParam(required = false, description = "Name") String name, + @ToolParam(required = false, description = "Email") String mail, + @ToolParam(required = false, description = "Phone number") String phone) + +@Tool(name = "delete_user", description = "Deletes a user") +public Map deleteUser(@ToolParam(required = false, description = "If not set, uses root realm") String realm, + @ToolParam(description = "Username (login)") String username) + +``` + +In JSON-RPC format: +```json +{ + "tools": [ + { + "name": "set_user_password", + "description": "Sets the password for a user", + "inputSchema": { + "type": "object", + "properties": { + "realm": { + "type": "string", + "description": "If not set, uses root realm" + }, + "username": { + "type": "string", + "description": "username" + }, + "password": { + "type": "string", + "description": "user password" + } + }, + "required": [ + "username", + "password" + ], + "additionalProperties": false + } + }, + { + "name": "set_user_attribute", + "description": "Sets the attribute value for a user", + "inputSchema": { + "type": "object", + "properties": { + "realm": { + "type": "string", + "description": "If not set, uses root realm" + }, + "username": { + "type": "string", + "description": "username" + }, + "attribute": { + "type": "string", + "description": "user attribute name" + }, + "value": { + "type": "string", + "description": "user attribute value" + } + }, + "required": [ + "username", + "attribute", + "value" + ], + "additionalProperties": false + } + }, + { + "name": "get_users", + "description": "Returns OpenAM user list from the default (root) realm", + "inputSchema": { + "type": "object", + "properties": { + "realm": { + "type": "string", + "description": "If not set, uses root realm" + }, + "filter": { + "type": "string", + "description": "Username filter" + } + }, + "required": [], + "additionalProperties": false + } + }, + { + "name": "create_user", + "description": "Creates a new user", + "inputSchema": { + "type": "object", + "properties": { + "realm": { + "type": "string", + "description": "If not set, uses root realm" + }, + "userName": { + "type": "string", + "description": "Username (login)" + }, + "password": { + "type": "string", + "description": "Password (min length 8)" + }, + "familyName": { + "type": "string", + "description": "User family name" + }, + "givenName": { + "type": "string", + "description": "User given name" + }, + "name": { + "type": "string", + "description": "Name" + }, + "mail": { + "type": "string", + "description": "Email" + }, + "phone": { + "type": "string", + "description": "Phone number" + } + }, + "required": [ + "userName", + "password" + ], + "additionalProperties": false + } + }, + { + "name": "delete_user", + "description": "Deletes a user", + "inputSchema": { + "type": "object", + "properties": { + "realm": { + "type": "string", + "description": "If not set, uses root realm" + }, + "username": { + "type": "string", + "description": "Username (login)" + } + }, + "required": [ + "username" + ], + "additionalProperties": false + } + } + ] +} + +``` diff --git a/openam-tools/openam-mcp-server/pom.xml b/openam-tools/openam-mcp-server/pom.xml new file mode 100644 index 0000000000..8952d3a738 --- /dev/null +++ b/openam-tools/openam-mcp-server/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + org.openidentityplatform.openam + openam-tools + 16.0.0-SNAPSHOT + + org.openidentityplatform.openam + openam-mcp-server + OpenAM MCP Server + MCP server for OpenAM management + + 17 + 3.5.6 + 1.1.0-M4 + ${java.version} + ${java.version} + + + + org.springframework.ai + spring-ai-starter-mcp-server-webmvc + + + com.github.ben-manes.caffeine + caffeine + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-starter-parent + ${spring-boot.version} + pom + import + + + org.springframework.ai + spring-ai-bom + ${spring-ai.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + true + + + + + + diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplication.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplication.java new file mode 100644 index 0000000000..30a0649ddb --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplication.java @@ -0,0 +1,33 @@ +package org.openidentityplatform.openam.mcp.server; + +import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; +import org.openidentityplatform.openam.mcp.server.service.UserService; +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.ai.tool.method.MethodToolCallbackProvider; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpHeaders; +import org.springframework.web.client.RestClient; + +@SpringBootApplication +@ConfigurationPropertiesScan +public class OpenAmMcpServerApplication { + public static void main(String[] args) { + SpringApplication.run(OpenAmMcpServerApplication.class, args); + } + + @Bean + public RestClient getOpenAMRestClient(OpenAMConfig openAMConfig) { + return RestClient.builder().baseUrl(openAMConfig.url()) + .defaultHeader(HttpHeaders.CONTENT_TYPE, "application/json").build(); + } + + @Bean + public ToolCallbackProvider getTools(UserService userService) { + return MethodToolCallbackProvider.builder().toolObjects(userService).build(); + } +} + + diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/OpenAMConfig.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/OpenAMConfig.java new file mode 100644 index 0000000000..5f37ba653b --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/OpenAMConfig.java @@ -0,0 +1,17 @@ +package org.openidentityplatform.openam.mcp.server.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("openam") +public record OpenAMConfig( + String url, + + boolean useOAuthForAuthentication, + + String oidcAuthChain, + String oidcAuthHeader, + + String tokenHeader, + String username, + String password +) {} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/WebConfig.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/WebConfig.java new file mode 100644 index 0000000000..52fe7fa047 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/WebConfig.java @@ -0,0 +1,22 @@ +package org.openidentityplatform.openam.mcp.server.config; + +import org.openidentityplatform.openam.mcp.server.security.AuthInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + private final AuthInterceptor authInterceptor; + + public WebConfig(AuthInterceptor authInterceptor) { + this.authInterceptor = authInterceptor; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(authInterceptor) + .addPathPatterns("/mcp") + .addPathPatterns("/mcp/**"); + } +} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java new file mode 100644 index 0000000000..afae79dd30 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java @@ -0,0 +1,49 @@ +package org.openidentityplatform.openam.mcp.server.controller; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestClient; + +import java.util.Set; + +@RestController +public class OAuth2Controller { + private final RestClient openAMRestClient; + + public OAuth2Controller(RestClient openAMRestClient) { + this.openAMRestClient = openAMRestClient; + } + + private static final Set IGNORE_HEADERS = Set.of( + "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", + "te", "trailers", "transfer-encoding", "upgrade" + ); + + @GetMapping("/.well-known/**") + public ResponseEntity openAMWellKnown(HttpServletRequest request) { + RestClient.RequestBodySpec requestSpec = openAMRestClient + .method(HttpMethod.valueOf(request.getMethod())) + .uri("/oauth2".concat(request.getRequestURI())) + .headers(headers -> request.getHeaderNames().asIterator().forEachRemaining(name -> { + if (IGNORE_HEADERS.contains(name.toLowerCase())) { + return; + } + String value = request.getHeader(name); + headers.add(name, value); + })); + try { + return requestSpec.retrieve() + .toEntity(String.class); + } catch (HttpClientErrorException e) { + return ResponseEntity.status(e.getStatusCode()) + .headers(e.getResponseHeaders()) + .body(e.getResponseBodyAs(String.class)); + } + + } + +} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/User.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/User.java new file mode 100644 index 0000000000..005512cee7 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/User.java @@ -0,0 +1,27 @@ +package org.openidentityplatform.openam.mcp.server.model; + +import java.util.List; + +public record User(String userName, + String familyName, + String givenName, + String name, + String mail, + String phone +) { + public User(UserDTO userDTO) { + this(userDTO.userName(), + singleValue(userDTO.sn()), + singleValue(userDTO.givenName()), + singleValue(userDTO.cn()), + singleValue(userDTO.mail()), + singleValue(userDTO.telephoneNumber())); + } + + private static String singleValue(List vals) { + if (vals == null || vals.isEmpty()) { + return null; + } + return vals.iterator().next(); + } +} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserDTO.java new file mode 100644 index 0000000000..0eb4d2ddca --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserDTO.java @@ -0,0 +1,30 @@ +package org.openidentityplatform.openam.mcp.server.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public record UserDTO( + @JsonProperty("username") + String userName, + + @JsonProperty("sn") + List sn, + + @JsonProperty("givenName") + List givenName, + + @JsonProperty("cn") + List cn, + + @JsonProperty("employeeNumber") + List employeeNumber, + + @JsonProperty("telephoneNumber") + List telephoneNumber, + + @JsonProperty("mail") + List mail + +) {} + diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserSearchResponse.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserSearchResponse.java new file mode 100644 index 0000000000..facf99a80c --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserSearchResponse.java @@ -0,0 +1,15 @@ +package org.openidentityplatform.openam.mcp.server.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public record UserSearchResponse(@JsonProperty("result") List result, + @JsonProperty("resultCount") int resultCount, + @JsonProperty("pagedResultsCookie") String pagedResultsCookie, + @JsonProperty("totalPagedResultsPolicy") String totalPagedResultsPolicy, + @JsonProperty("totalPagedResults") int totalPagedResults, + @JsonProperty("remainingPagedResults") int remainingPagedResults) +{ +} + diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java new file mode 100644 index 0000000000..beed128f7d --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java @@ -0,0 +1,164 @@ +package org.openidentityplatform.openam.mcp.server.security; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestClient; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.io.IOException; +import java.time.Duration; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@Component +public class AuthInterceptor implements HandlerInterceptor { + + private static final String LOGIN_PASSWORD_TOKEN_KEY = "login-password-token"; + + private static final Logger log = LoggerFactory.getLogger(AuthInterceptor.class); + + private final RestClient openAMRestClient; + + private final OpenAMConfig openAMConfig; + + + private final Cache tokenCache = Caffeine.newBuilder() + .expireAfterWrite(30, TimeUnit.MINUTES).build(); + + public AuthInterceptor(RestClient openAMRestClient, OpenAMConfig openAMConfig) { + this.openAMRestClient = openAMRestClient; + this.openAMConfig = openAMConfig; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if(openAMConfig.useOAuthForAuthentication()) { + return preHandleOAuth(request, response); + } else { + return preHandleUsernamePassword(request); + } + } + + private long tokenValidSeconds(String tokenId) { + String sessionUri = "/json/sessions/?_action=getSessionInfo"; + try { + Map tokenProps = openAMRestClient.post() + .uri(sessionUri) + .header(openAMConfig.tokenHeader(), tokenId) + .retrieve().body(new ParameterizedTypeReference<>() { + }); + String expTime = tokenProps.get("maxIdleExpirationTime"); + ZonedDateTime dateTime = ZonedDateTime.parse(expTime); + ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); + Duration duration = Duration.between(now, dateTime); + return duration.getSeconds(); + } catch (Exception e) { + log.warn("error getting token properties: ", e); + return -1; + } + } + + private String getUserNamePasswordToken() { + Map tokenResponse = openAMRestClient.post().uri("/json/authenticate") + .header("X-OpenAM-Username", openAMConfig.username()) + .header("X-OpenAM-Password", openAMConfig.password()) + .retrieve() + .body(new ParameterizedTypeReference<>() { + }); + return tokenResponse.get("tokenId"); + } + + private String getTokenIdFromAccessToken(String accessToken) { + Map tokenResponse = openAMRestClient.post() + .uri("/json/authenticate?authIndexType=service&authIndexValue=".concat(openAMConfig.oidcAuthChain())) + .header(openAMConfig.oidcAuthHeader(), accessToken) + .body("{}") + .accept(MediaType.APPLICATION_JSON) + .retrieve() + .body(new ParameterizedTypeReference<>() {}); + return tokenResponse.get("tokenId"); + } + + private boolean preHandleUsernamePassword(HttpServletRequest request) { + final String token; + try { + token = tokenCache.get(LOGIN_PASSWORD_TOKEN_KEY, (k) -> getUserNamePasswordToken()); + } catch (Exception e) { + log.warn("preHandleUsernamePassword: error getting token:", e); + throw e; + } + long seconds = tokenValidSeconds(token); + if(seconds > 1) { + request.setAttribute("tokenId", token); + return true; + } + log.info("preHandleUsernamePassword: token {} is about to expire in {} s", token, seconds); + tokenCache.invalidate(LOGIN_PASSWORD_TOKEN_KEY); + return preHandleUsernamePassword(request); + } + + private boolean preHandleOAuth(HttpServletRequest request, HttpServletResponse response) throws IOException { + + String authHeader = request.getHeader("Authorization"); + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + return false; + } + String accessToken = authHeader.substring(7); + + if (!accessTokenValid(accessToken)) { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setHeader("WWW-Authenticate", "Bearer realm=\"OpenAM\""); + response.setContentType("application/json"); + response.getWriter().write("{\"error\":\"Unauthorized\"}"); + return false; + } + final String token; + try { + token = tokenCache.get(accessToken, this::getTokenIdFromAccessToken); + } catch (Exception e) { + log.warn("error getting token:", e); + throw e; + } + long seconds = tokenValidSeconds(token); + if(seconds > 1) { + request.setAttribute("tokenId", token); + return true; + } + log.info("preHandleOAuth: token {} is about to expire in {} s", token, seconds); + tokenCache.invalidate(accessToken); + return preHandleOAuth(request, response); + } + + private boolean accessTokenValid(String accessToken) { + + //validate access token + try { + Map response = openAMRestClient.get() + .uri("/oauth2/userinfo") + .header("Authorization", "Bearer " + accessToken) + .retrieve() + .body(new ParameterizedTypeReference<>() { + }); + if(response.containsKey("name")) { + return true; + } else { + log.warn("got invalid response: {} for access token: {}", response, accessToken); + return false; + } + } catch (Exception e) { + log.warn("Token validation failed: {}", e.getMessage()); + return false; + } + } +} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java new file mode 100644 index 0000000000..ed53128e6c --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java @@ -0,0 +1,169 @@ +package org.openidentityplatform.openam.mcp.server.service; + +import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; +import org.openidentityplatform.openam.mcp.server.model.User; +import org.openidentityplatform.openam.mcp.server.model.UserDTO; +import org.openidentityplatform.openam.mcp.server.model.UserSearchResponse; +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.ai.tool.annotation.ToolParam; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +public class UserService { + private final RestClient openAMRestClient; + + private final OpenAMConfig openAMConfig; + + private final static String DEFAULT_REALM = "root"; + + public UserService(RestClient openAMRestClient, OpenAMConfig openAMConfig) { + this.openAMRestClient = openAMRestClient; + this.openAMConfig = openAMConfig; + } + + @Tool(name = "get_users", description = "Returns OpenAM user list from the default (root) realm") + public List getUsers(@ToolParam(required = false, description = "If not set, uses root realm") String realm, + @ToolParam(required = false, description = "Username filter") String filter) { + + realm = getRealmOrDefault(realm); + + String queryFilter = "true"; + if(filter != null) { + queryFilter= "cn sw \"".concat(filter).concat("\""); + } + String uri = String.format("/json/realms/%s/users?_queryFilter=%s", realm, queryFilter); + String tokenId = getTokenId(); + UserSearchResponse userSearchResponse = openAMRestClient.get().uri(uri) + .header(openAMConfig.tokenHeader(), tokenId) + .retrieve() + .body(UserSearchResponse.class); + return userSearchResponse.result().stream().map(User::new).collect(Collectors.toList()); + } + + private static final Map ATTR_MAP = Map.of("familyName", "sn", + "givenName", "givenName", + "name", "cn", + "mail", "mail", + "phone", "telephoneNumber"); + + @Tool(name = "set_user_attribute", description = "Sets the attribute value for a user") + public User setUserAttribute(@ToolParam(required = false, description = "If not set, uses root realm") String realm, + @ToolParam(description = "username") String username, + @ToolParam(description = "user attribute name") String attribute, + @ToolParam(description = "user attribute value") String value) { + + realm = getRealmOrDefault(realm); + + if(!ATTR_MAP.containsKey(attribute)) { + throw new RuntimeException(String.format("invalid attribute: %s; allowed values %s", attribute, ATTR_MAP.keySet())); + } + String tokenId = getTokenId(); + + Map requestBody = Map.of(ATTR_MAP.get(attribute), value); + String uri = String.format("/json/realms/%s/users/%s", realm, username); + UserDTO user = openAMRestClient.put().uri(uri).body(requestBody) + .header(openAMConfig.tokenHeader(), tokenId) + .header("Accept-API-Version","resource=2.0, protocol=1.0") + .retrieve() + .body(UserDTO.class); + return new User(user); + } + + + @Tool(name = "set_user_password", description = "Sets the password for a user") + public User setUserPassword(@ToolParam(required = false, description = "If not set, uses root realm") String realm, + @ToolParam(description = "username") String username, + @ToolParam(description = "user password") String password) { + + realm = getRealmOrDefault(realm); + + String tokenId = getTokenId(); + + Map requestBody = Map.of("userpassword", password); + String uri = String.format("/json/realms/%s/users/%s?_action=changePassword", realm, username); + UserDTO user = openAMRestClient.put().uri(uri).body(requestBody) + .header(openAMConfig.tokenHeader(), tokenId) + .header("Accept-API-Version","resource=2.0, protocol=1.0") + .retrieve() + .body(UserDTO.class); + return new User(user); + } + + @Tool(name = "create_user", description = "Creates a new user") + public User createUser(@ToolParam(required = false, description = "If not set, uses root realm") String realm, + @ToolParam(description = "Username (login)") String userName, + @ToolParam(description = "Password (min length 8)") String password, + @ToolParam(required = false, description = "User family name") String familyName, + @ToolParam(required = false, description = "User given name") String givenName, + @ToolParam(required = false, description = "Name") String name, + @ToolParam(required = false, description = "Email") String mail, + @ToolParam(required = false, description = "Phone number") String phone + ) { + + realm = getRealmOrDefault(realm); + + Map userProps = new HashMap<>(); + userProps.put("username", userName); + userProps.put("userpassword", password); + if(familyName != null) { + userProps.put(ATTR_MAP.get("familyName"), familyName); + } + if(givenName != null) { + userProps.put(ATTR_MAP.get("givenName"), givenName); + } + if(name != null) { + userProps.put(ATTR_MAP.get("name"), name); + } + if(mail != null) { + userProps.put(ATTR_MAP.get("mail"), mail); + } + if(phone != null) { + userProps.put(ATTR_MAP.get("phone"), phone); + } + + String tokenId = getTokenId(); + + String uri = String.format("/json/realms/%s/users/?_action=create", realm); + UserDTO user = openAMRestClient.post().uri(uri).body(userProps) + .header(openAMConfig.tokenHeader(), tokenId) + .header("Accept-API-Version","resource=2.0, protocol=1.0") + .retrieve() + .body(UserDTO.class); + return new User(user); + } + + @Tool(name = "delete_user", description = "Deletes a user") + public Map deleteUser(@ToolParam(required = false, description = "If not set, uses root realm") String realm, + @ToolParam(description = "Username (login)") String username) { + + realm = getRealmOrDefault(realm); + + String tokenId = getTokenId(); + + String uri = String.format("/json/realms/%s/users/%s", realm, username); + return openAMRestClient.delete().uri(uri) + .header(openAMConfig.tokenHeader(), tokenId) + .header("Accept-API-Version","resource=2.0, protocol=1.0") + .retrieve() + .body(new ParameterizedTypeReference<>() {}); + } + + private String getRealmOrDefault(String realm) { + return realm != null ? realm : DEFAULT_REALM; + } + + private String getTokenId() { + RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); + return (String) attrs.getAttribute("tokenId", RequestAttributes.SCOPE_REQUEST); + } + +} diff --git a/openam-tools/openam-mcp-server/src/main/resources/application.yml b/openam-tools/openam-mcp-server/src/main/resources/application.yml new file mode 100644 index 0000000000..9fb20a311c --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/resources/application.yml @@ -0,0 +1,26 @@ +server: + port: 8081 +spring: + application: + name: OpenAM MCP Server + ai: + mcp: + server: + name: openam-mcp-server + version: 0.0.1 + protocol: STATELESS + +openam: + url: ${OPENAM_URL:http://openam.example.org:8080/openam} + tokenHeader: ${OPENAM_TOKEN_HEADER:iPlanetDirectoryPro} + useOAuthForAuthentication: ${OPENAM_USE_OAUTH:false} + username: ${OPENAM_ADMIN_USERNAME:amadmin} + password: ${OPENAM_ADMIN_PASSWORD:ampassword} + oidcAuthChain: ${OPENAM_OIDC_AUTH_CHAIN:oidc} + oidcAuthHeader: ${OPENAM_OIDC_AUTH_HEADER:oidc_id_token} + + +logging: + level: + org: + #springframework: DEBUG \ No newline at end of file diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplicationTests.java b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplicationTests.java new file mode 100644 index 0000000000..39cb4fe150 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplicationTests.java @@ -0,0 +1,12 @@ +package org.openidentityplatform.openam.mcp.server; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class OpenAmMcpServerApplicationTests { + @Test + void contextLoads() { + } + +} diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java new file mode 100644 index 0000000000..d1db7a4de7 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java @@ -0,0 +1,125 @@ +package org.openidentityplatform.openam.mcp.server.service; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; +import org.openidentityplatform.openam.mcp.server.model.User; +import org.openidentityplatform.openam.mcp.server.model.UserDTO; +import org.openidentityplatform.openam.mcp.server.model.UserSearchResponse; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.client.RestClient; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class UserServiceTest { + + private final ObjectMapper objectMapper; + + public UserServiceTest() { + this.objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + UserService userService = null; + RestClient restClient; + RestClient.RequestHeadersUriSpec requestHeadersUriSpec; + RestClient.RequestBodyUriSpec requestBodyUriSpec; + RestClient.RequestBodySpec requestBodySpec; + RestClient.ResponseSpec responseSpec; + + @BeforeEach + public void setupMocks() { + MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + mockRequest.setAttribute("tokenId", "test-token-id"); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(mockRequest)); + + restClient = mock(RestClient.class); + requestHeadersUriSpec = mock(RestClient.RequestBodyUriSpec.class); + requestBodyUriSpec = mock(RestClient.RequestBodyUriSpec.class); + requestBodySpec = mock(RestClient.RequestBodySpec.class); + responseSpec = mock(RestClient.ResponseSpec.class); + + + when(restClient.get()).thenReturn(requestHeadersUriSpec); + when(requestHeadersUriSpec.uri(anyString())).thenReturn(requestBodySpec); + when(requestBodySpec.header(anyString(), anyString())).thenReturn(requestBodySpec); + when(requestBodySpec.retrieve()).thenReturn(responseSpec); + + when(restClient.put()).thenReturn(requestBodyUriSpec); + when(requestBodyUriSpec.uri(anyString())).thenReturn(requestBodyUriSpec); + when(requestBodyUriSpec.body(anyMap())).thenReturn(requestBodyUriSpec); + when(requestBodyUriSpec.header(anyString(), anyString())).thenReturn(requestBodyUriSpec); + when(requestBodyUriSpec.retrieve()).thenReturn(responseSpec); + + when(restClient.delete()).thenReturn(requestHeadersUriSpec); + + userService = new UserService(restClient, new OpenAMConfig( + null, + false, + null, + null, + "iPlanetDirectoryPro", + null, + null + )); + + } + + @Test + void getUsersTest() throws Exception { + + InputStream is = getClass().getClassLoader().getResourceAsStream("users/users-list-response.json"); + UserSearchResponse userSearchResponse = objectMapper.readValue(is, UserSearchResponse.class); + + when(responseSpec.body(UserSearchResponse.class)).thenReturn(userSearchResponse); + + List userList = userService.getUsers(null, null); + assertEquals(userList.size(), 2); + } + + @Test + void setUserAttributeTest() throws IOException { + InputStream is = getClass().getClassLoader().getResourceAsStream("users/user-response.json"); + UserDTO userDTO = objectMapper.readValue(is, UserDTO.class); + + when(responseSpec.body(UserDTO.class)).thenReturn(userDTO); + + User user = userService.setUserAttribute(null, "demo", "familyName", "Flintstone"); + assertNotNull(user); + } + + @Test + void setUserPassword() throws IOException { + InputStream is = getClass().getClassLoader().getResourceAsStream("users/user-response.json"); + UserDTO userDTO = objectMapper.readValue(is, UserDTO.class); + + when(responseSpec.body(UserDTO.class)).thenReturn(userDTO); + + User user = userService.setUserPassword(null, "demo", "passw0rd"); + assertNotNull(user); + } + + @Test + void deleteUser() { + when(responseSpec.body(eq(new ParameterizedTypeReference>() {}))).thenReturn(Map.of("success", "true")); + + Map result = userService.deleteUser(null, "demo"); + assertEquals("true", result.get("success")); + } +} \ No newline at end of file diff --git a/openam-tools/openam-mcp-server/src/test/resources/users/user-response.json b/openam-tools/openam-mcp-server/src/test/resources/users/user-response.json new file mode 100644 index 0000000000..cf3eda379e --- /dev/null +++ b/openam-tools/openam-mcp-server/src/test/resources/users/user-response.json @@ -0,0 +1,76 @@ +{ + "username": "demo", + "realm": "dc=openam,dc=forgerock,dc=org", + "uid": [ + "demo" + ], + "createTimestamp": [ + "20160108155628Z" + ], + "inetUserStatus": [ + "Active" + ], + "mail": [ + "demo.user@example.com" + ], + "sn": [ + "demo" + ], + "cn": [ + "demo" + ], + "objectClass": [ + "devicePrintProfilesContainer", + "person", + "sunIdentityServerLibertyPPService", + "sunFederationManagerDataStore", + "inetorgperson", + "oathDeviceProfilesContainer", + "iPlanetPreferences", + "iplanet-am-auth-configuration-service", + "sunFMSAML2NameIdentifier", + "organizationalperson", + "inetuser", + "kbaInfoContainer", + "forgerock-am-dashboard-service", + "iplanet-am-managed-person", + "iplanet-am-user-service", + "sunAMAuthAccountLockout", + "top" + ], + "kbaInfo": [ + { + "questionId": "2", + "answer": { + "$crypto": { + "value": { + "algorithm": "SHA-256", + "data": "VXGtsnjJMC...MQJ/goU5hkfF" + }, + "type": "salted-hash" + } + } + }, + { + "questionId": "1", + "answer": { + "$crypto": { + "value": { + "algorithm": "SHA-256", + "data": "cfYYzi9U...rVfFl0Tdw0iX" + }, + "type": "salted-hash" + } + } + } + ], + "dn": [ + "uid=demo,ou=people,dc=openam,dc=forgerock,dc=org" + ], + "universalid": [ + "id=demo,ou=user,dc=openam,dc=forgerock,dc=org" + ], + "modifyTimestamp": [ + "20160113010610Z" + ] +} \ No newline at end of file diff --git a/openam-tools/openam-mcp-server/src/test/resources/users/users-list-response.json b/openam-tools/openam-mcp-server/src/test/resources/users/users-list-response.json new file mode 100644 index 0000000000..c26d36c7cc --- /dev/null +++ b/openam-tools/openam-mcp-server/src/test/resources/users/users-list-response.json @@ -0,0 +1,119 @@ +{ + "result": [ + { + "username": "amAdmin", + "realm": "dc=openam,dc=forgerock,dc=org", + "sunIdentityMSISDNNumber": [], + "mail": [], + "sn": [ + "amAdmin" + ], + "givenName": [ + "amAdmin" + ], + "universalid": [ + "id=amAdmin,ou=user,dc=openam,dc=forgerock,dc=org" + ], + "cn": [ + "amAdmin" + ], + "iplanet-am-user-success-url": [], + "telephoneNumber": [], + "roles": [ + "ui-global-admin", + "ui-realm-admin" + ], + "iplanet-am-user-failure-url": [], + "inetuserstatus": [ + "Active" + ], + "postalAddress": [], + "dn": [ + "uid=amAdmin,ou=people,dc=openam,dc=forgerock,dc=org" + ], + "employeeNumber": [], + "iplanet-am-user-alias-list": [] + }, + { + "username": "demo", + "realm": "dc=openam,dc=forgerock,dc=org", + "uid": [ + "demo" + ], + "createTimestamp": [ + "20160108155628Z" + ], + "inetUserStatus": [ + "Active" + ], + "mail": [ + "demo.user@example.com" + ], + "sn": [ + "demo" + ], + "cn": [ + "demo" + ], + "objectClass": [ + "devicePrintProfilesContainer", + "person", + "sunIdentityServerLibertyPPService", + "sunFederationManagerDataStore", + "inetorgperson", + "oathDeviceProfilesContainer", + "iPlanetPreferences", + "iplanet-am-auth-configuration-service", + "sunFMSAML2NameIdentifier", + "organizationalperson", + "inetuser", + "kbaInfoContainer", + "forgerock-am-dashboard-service", + "iplanet-am-managed-person", + "iplanet-am-user-service", + "sunAMAuthAccountLockout", + "top" + ], + "kbaInfo": [ + { + "questionId": "2", + "answer": { + "$crypto": { + "value": { + "algorithm": "SHA-256", + "data": "VXGtsnjJMC...MQJ/goU5hkfF" + }, + "type": "salted-hash" + } + } + }, + { + "questionId": "1", + "answer": { + "$crypto": { + "value": { + "algorithm": "SHA-256", + "data": "cfYYzi9U...rVfFl0Tdw0iX" + }, + "type": "salted-hash" + } + } + } + ], + "dn": [ + "uid=demo,ou=people,dc=openam,dc=forgerock,dc=org" + ], + "universalid": [ + "id=demo,ou=user,dc=openam,dc=forgerock,dc=org" + ], + "modifyTimestamp": [ + "20160113010610Z" + ] + } + ], + "resultCount": 2, + "pagedResultsCookie": null, + "totalPagedResultsPolicy": "NONE", + "totalPagedResults": -1, + "remainingPagedResults": -1 +} \ No newline at end of file diff --git a/openam-tools/pom.xml b/openam-tools/pom.xml index ff66c2b371..5ec2acc187 100755 --- a/openam-tools/pom.xml +++ b/openam-tools/pom.xml @@ -93,5 +93,26 @@ + + + jdk17a + + [17,) + + + build-helper-plugin + openam-build-tools + openam-configurator-tool + openam-installtools + openam-installtools-launcher + openam-upgrade-tool + openam-license-core + openam-license-manager-cli + openam-installer-utils + openam-license-servlet + openam-mcp-server + + + From 04a7d19aab3cc31230ac0df689e1c69eb91e225a Mon Sep 17 00:00:00 2001 From: maximthomas Date: Fri, 7 Nov 2025 10:45:47 +0300 Subject: [PATCH 02/14] OpenAM MCP Server: authentication configuration --- .../openam/mcp/server/model/AuthChain.java | 6 + .../openam/mcp/server/model/AuthChainDTO.java | 11 ++ .../mcp/server/model/CoreAuthModule.java | 7 + .../mcp/server/model/CoreAuthModuleDTO.java | 5 + .../openam/mcp/server/model/ModuleDTO.java | 5 + .../mcp/server/model/PropertySchemaDTO.java | 3 + .../openam/mcp/server/model/Realm.java | 15 ++ .../openam/mcp/server/model/RealmDTO.java | 13 ++ .../openam/mcp/server/model/SchemaDTO.java | 6 + .../mcp/server/model/SearchResponseDTO.java | 13 ++ .../mcp/server/model/UserSearchResponse.java | 15 -- .../service/AuthenticationConfigService.java | 113 ++++++++++++ .../server/service/OpenAMAbstractService.java | 30 +++ .../mcp/server/service/RealmService.java | 30 +++ .../mcp/server/service/UserService.java | 29 +-- .../AuthenticationConfigServiceTest.java | 44 +++++ .../mcp/server/service/OpenAMServiceTest.java | 69 +++++++ .../mcp/server/service/RealmServiceTest.java | 40 ++++ .../mcp/server/service/UserServiceTest.java | 70 +------ .../resources/auth/all-modules-response.json | 174 ++++++++++++++++++ .../test/resources/auth/chains-response.json | 97 ++++++++++ .../test/resources/auth/modules-response.json | 59 ++++++ .../resources/realms/realms-response.json | 26 +++ 23 files changed, 780 insertions(+), 100 deletions(-) create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChain.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChainDTO.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModule.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModuleDTO.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/ModuleDTO.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/PropertySchemaDTO.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/RealmDTO.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SchemaDTO.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SearchResponseDTO.java delete mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserSearchResponse.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/RealmService.java create mode 100644 openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java create mode 100644 openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/OpenAMServiceTest.java create mode 100644 openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java create mode 100644 openam-tools/openam-mcp-server/src/test/resources/auth/all-modules-response.json create mode 100644 openam-tools/openam-mcp-server/src/test/resources/auth/chains-response.json create mode 100644 openam-tools/openam-mcp-server/src/test/resources/auth/modules-response.json create mode 100644 openam-tools/openam-mcp-server/src/test/resources/realms/realms-response.json diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChain.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChain.java new file mode 100644 index 0000000000..b17c219779 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChain.java @@ -0,0 +1,6 @@ +package org.openidentityplatform.openam.mcp.server.model; + +import java.util.List; + +public record AuthChain(String id, List modules) { +} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChainDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChainDTO.java new file mode 100644 index 0000000000..51b54174ef --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChainDTO.java @@ -0,0 +1,11 @@ +package org.openidentityplatform.openam.mcp.server.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public record AuthChainDTO(@JsonProperty("_id") String id, + @JsonProperty("authChainConfiguration") List modules) { + + public record AuthChainModuleDTO(String module) {} +} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModule.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModule.java new file mode 100644 index 0000000000..e2464736ef --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModule.java @@ -0,0 +1,7 @@ +package org.openidentityplatform.openam.mcp.server.model; + +public record CoreAuthModule(String id, String name) { + public CoreAuthModule(CoreAuthModuleDTO coreAuthModuleDTO) { + this(coreAuthModuleDTO.id(), coreAuthModuleDTO.name()); + } +} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModuleDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModuleDTO.java new file mode 100644 index 0000000000..fa118bb495 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModuleDTO.java @@ -0,0 +1,5 @@ +package org.openidentityplatform.openam.mcp.server.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record CoreAuthModuleDTO(@JsonProperty("_id") String id, String name) {} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/ModuleDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/ModuleDTO.java new file mode 100644 index 0000000000..922b928137 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/ModuleDTO.java @@ -0,0 +1,5 @@ +package org.openidentityplatform.openam.mcp.server.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record ModuleDTO(@JsonProperty("_id") String id, String typeDescription, String type) {} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/PropertySchemaDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/PropertySchemaDTO.java new file mode 100644 index 0000000000..906f880e97 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/PropertySchemaDTO.java @@ -0,0 +1,3 @@ +package org.openidentityplatform.openam.mcp.server.model; + +public record PropertySchemaDTO(String title, String description) {} \ No newline at end of file diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java new file mode 100644 index 0000000000..44a79c13bc --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java @@ -0,0 +1,15 @@ +package org.openidentityplatform.openam.mcp.server.model; + +import java.util.ArrayList; +import java.util.List; + +public record Realm(String name, + boolean active, + String parentPath, + List aliases) { + public Realm(RealmDTO realmDTO) { + this(realmDTO.name().equals("/") ? "root" : realmDTO.name(), + realmDTO.active(), realmDTO.parentPath(), + new ArrayList<>(realmDTO.aliases())); + } +} \ No newline at end of file diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/RealmDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/RealmDTO.java new file mode 100644 index 0000000000..b1b8744e96 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/RealmDTO.java @@ -0,0 +1,13 @@ +package org.openidentityplatform.openam.mcp.server.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public record RealmDTO(@JsonProperty("_id") String id, + @JsonProperty("parentPath") String parentPath, + @JsonProperty("active") boolean active, + @JsonProperty("name") String name, + @JsonProperty("aliases") List aliases) { + +} \ No newline at end of file diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SchemaDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SchemaDTO.java new file mode 100644 index 0000000000..79a67b10e1 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SchemaDTO.java @@ -0,0 +1,6 @@ +package org.openidentityplatform.openam.mcp.server.model; + +import java.util.Map; + +public record SchemaDTO(String type, Map properties) { +} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SearchResponseDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SearchResponseDTO.java new file mode 100644 index 0000000000..98cb4325a7 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SearchResponseDTO.java @@ -0,0 +1,13 @@ +package org.openidentityplatform.openam.mcp.server.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public record SearchResponseDTO(@JsonProperty("result") List result, + @JsonProperty("resultCount") int resultCount, + @JsonProperty("pagedResultsCookie") String pagedResultsCookie, + @JsonProperty("totalPagedResultsPolicy") String totalPagedResultsPolicy, + @JsonProperty("totalPagedResults") int totalPagedResults, + @JsonProperty("remainingPagedResults") int remainingPagedResults) { +} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserSearchResponse.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserSearchResponse.java deleted file mode 100644 index facf99a80c..0000000000 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserSearchResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.openidentityplatform.openam.mcp.server.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.List; - -public record UserSearchResponse(@JsonProperty("result") List result, - @JsonProperty("resultCount") int resultCount, - @JsonProperty("pagedResultsCookie") String pagedResultsCookie, - @JsonProperty("totalPagedResultsPolicy") String totalPagedResultsPolicy, - @JsonProperty("totalPagedResults") int totalPagedResults, - @JsonProperty("remainingPagedResults") int remainingPagedResults) -{ -} - diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java new file mode 100644 index 0000000000..8de06bd526 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java @@ -0,0 +1,113 @@ +package org.openidentityplatform.openam.mcp.server.service; + +import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; +import org.openidentityplatform.openam.mcp.server.model.AuthChain; +import org.openidentityplatform.openam.mcp.server.model.AuthChainDTO; +import org.openidentityplatform.openam.mcp.server.model.CoreAuthModule; +import org.openidentityplatform.openam.mcp.server.model.CoreAuthModuleDTO; +import org.openidentityplatform.openam.mcp.server.model.ModuleDTO; +import org.openidentityplatform.openam.mcp.server.model.PropertySchemaDTO; +import org.openidentityplatform.openam.mcp.server.model.SchemaDTO; +import org.openidentityplatform.openam.mcp.server.model.SearchResponseDTO; +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.ai.tool.annotation.ToolParam; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +public class AuthenticationConfigService extends OpenAMAbstractService { + public AuthenticationConfigService(RestClient openAMRestClient, OpenAMConfig openAMConfig) { + super(openAMRestClient, openAMConfig); + } + + @Tool(name = "get_auth_modules", description = "Returns OpenAM authentication modules list") + public void getAuthModules(@ToolParam(required = false, description = "If not set, uses root realm") String realm) { + realm = getRealmOrDefault(realm); + String tokenId = getTokenId(); + + List realmAuthModules = getRealmAuthModules(realm, tokenId); + for(ModuleDTO moduleDTO : realmAuthModules) { + Map schema = getModuleSchema(tokenId, realm, moduleDTO.type()); + Map settings = getModuleSettings(tokenId, realm, moduleDTO.id(), moduleDTO.type()); + } + + } + + @Tool(name = "get_auth_chains", description = "Returns OpenAM authentication chains with modules") + public List getOpenAMAuthChains(@ToolParam(required = false, description = "If not set, uses root realm") String realm) { + realm = getRealmOrDefault(realm); + String tokenId = getTokenId(); + + final List chainsDTOList = getRealmAuthChains(realm, tokenId); + final List moduleDTOList = getRealmAuthModules(realm, tokenId); + + List chains = chainsDTOList.stream().map(chainDTO -> { + List chainModules = chainDTO.modules().stream().map(AuthChainDTO.AuthChainModuleDTO::module).toList(); + List moduleNames = chainModules.stream() + .map(cm -> moduleDTOList.stream() + .filter(rm -> cm.equals(rm.id())).findFirst().get().typeDescription()).toList(); + return new AuthChain(chainDTO.id(), moduleNames); + }).collect(Collectors.toList()); + + return chains; + } + + @Tool(name = "get_available_module_list", description = "Returns all avialable authenticaion modules") + public List getAvailableModuleList() { + String tokenId = getTokenId(); + String coreModulesUri = "/json/global-config/authentication/modules?_action=getAllTypes"; + SearchResponseDTO coreAuthModulesResponse = openAMRestClient.post().uri(coreModulesUri) + .header(openAMConfig.tokenHeader(), tokenId) + .retrieve() + .body(new ParameterizedTypeReference<>() {}); + return coreAuthModulesResponse.result().stream().map(CoreAuthModule::new).collect(Collectors.toList()); + } + + List getRealmAuthChains(String realm, String tokenId) { + final String chainsUri = String.format("/json/realms/%s/realm-config/authentication/chains?_queryFilter=true", realm); + SearchResponseDTO authChainsResponse = openAMRestClient.get().uri(chainsUri) + .header(openAMConfig.tokenHeader(), tokenId) + .retrieve() + .body(new ParameterizedTypeReference<>() { + }); + return authChainsResponse.result(); + } + + List getRealmAuthModules(String realm, String tokenId) { + final String modulesUri = String.format("/json/realms/%s/realm-config/authentication/modules?_queryFilter=true", realm); + SearchResponseDTO modulesResponse = openAMRestClient.get().uri(modulesUri) + .header(openAMConfig.tokenHeader(), tokenId) + .retrieve() + .body(new ParameterizedTypeReference<>() { + }); + return modulesResponse.result(); + } + + Map getModuleSchema(String tokenId, String realm, String moduleType) { + + String schemaUrl = String.format( "/json/realms/%s/realm-config/authentication/modules/%s?_action=schema", realm, moduleType); + SchemaDTO schemaDTO = openAMRestClient.post().uri(schemaUrl) + .header(openAMConfig.tokenHeader(), tokenId) + .retrieve() + .body(SchemaDTO.class); + + return schemaDTO.properties(); + } + + Map getModuleSettings(String tokenId, String realm, String moduleId, String moduleType) { + String settingsUrl = String.format("/json/realms/%s/realm-config/authentication/modules/%s/%s", realm, moduleType, moduleId); + + return openAMRestClient.get().uri(settingsUrl) + .header(openAMConfig.tokenHeader(), tokenId) + .retrieve() + .body(new ParameterizedTypeReference<>() { + }); + } +} + + diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java new file mode 100644 index 0000000000..619572c6b3 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java @@ -0,0 +1,30 @@ +package org.openidentityplatform.openam.mcp.server.service; + +import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; +import org.springframework.web.client.RestClient; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +public abstract class OpenAMAbstractService { + + protected final RestClient openAMRestClient; + + protected final OpenAMConfig openAMConfig; + + protected final static String DEFAULT_REALM = "root"; + + public OpenAMAbstractService(RestClient openAMRestClient, OpenAMConfig openAMConfig) { + this.openAMRestClient = openAMRestClient; + this.openAMConfig = openAMConfig; + } + + protected String getTokenId() { + RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); + return (String) attrs.getAttribute("tokenId", RequestAttributes.SCOPE_REQUEST); + } + + protected String getRealmOrDefault(String realm) { + return realm != null ? realm : DEFAULT_REALM; + } + +} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/RealmService.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/RealmService.java new file mode 100644 index 0000000000..3e667447a9 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/RealmService.java @@ -0,0 +1,30 @@ +package org.openidentityplatform.openam.mcp.server.service; + +import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; +import org.openidentityplatform.openam.mcp.server.model.Realm; +import org.openidentityplatform.openam.mcp.server.model.RealmDTO; +import org.openidentityplatform.openam.mcp.server.model.SearchResponseDTO; +import org.springframework.ai.tool.annotation.Tool; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.web.client.RestClient; + +import java.util.List; +import java.util.stream.Collectors; + +public class RealmService extends OpenAMAbstractService { + public RealmService(RestClient openAMRestClient, OpenAMConfig openAMConfig) { + super(openAMRestClient, openAMConfig); + } + + @Tool(name = "get_realm", description = "Returns OpenAM realm list") + public List getRealms() { + String uri = "/json/global-config/realms?_queryFilter=true"; + String tokenId = getTokenId(); + SearchResponseDTO realmsResponse = openAMRestClient.get().uri(uri) + .header(openAMConfig.tokenHeader(), tokenId) + .retrieve() + .body(new ParameterizedTypeReference<>() { + }); + return realmsResponse.result().stream().map(Realm::new).collect(Collectors.toList()); + } +} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java index ed53128e6c..df79ba87ec 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java @@ -1,16 +1,14 @@ package org.openidentityplatform.openam.mcp.server.service; import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; +import org.openidentityplatform.openam.mcp.server.model.SearchResponseDTO; import org.openidentityplatform.openam.mcp.server.model.User; import org.openidentityplatform.openam.mcp.server.model.UserDTO; -import org.openidentityplatform.openam.mcp.server.model.UserSearchResponse; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.core.ParameterizedTypeReference; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient; -import org.springframework.web.context.request.RequestAttributes; -import org.springframework.web.context.request.RequestContextHolder; import java.util.HashMap; import java.util.List; @@ -18,16 +16,9 @@ import java.util.stream.Collectors; @Service -public class UserService { - private final RestClient openAMRestClient; - - private final OpenAMConfig openAMConfig; - - private final static String DEFAULT_REALM = "root"; - +public class UserService extends OpenAMAbstractService { public UserService(RestClient openAMRestClient, OpenAMConfig openAMConfig) { - this.openAMRestClient = openAMRestClient; - this.openAMConfig = openAMConfig; + super(openAMRestClient, openAMConfig); } @Tool(name = "get_users", description = "Returns OpenAM user list from the default (root) realm") @@ -42,10 +33,10 @@ public List getUsers(@ToolParam(required = false, description = "If not se } String uri = String.format("/json/realms/%s/users?_queryFilter=%s", realm, queryFilter); String tokenId = getTokenId(); - UserSearchResponse userSearchResponse = openAMRestClient.get().uri(uri) + SearchResponseDTO userSearchResponse = openAMRestClient.get().uri(uri) .header(openAMConfig.tokenHeader(), tokenId) .retrieve() - .body(UserSearchResponse.class); + .body(new ParameterizedTypeReference<>() {}); return userSearchResponse.result().stream().map(User::new).collect(Collectors.toList()); } @@ -156,14 +147,4 @@ public Map deleteUser(@ToolParam(required = false, description = .retrieve() .body(new ParameterizedTypeReference<>() {}); } - - private String getRealmOrDefault(String realm) { - return realm != null ? realm : DEFAULT_REALM; - } - - private String getTokenId() { - RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); - return (String) attrs.getAttribute("tokenId", RequestAttributes.SCOPE_REQUEST); - } - } diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java new file mode 100644 index 0000000000..074d975caa --- /dev/null +++ b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java @@ -0,0 +1,44 @@ +package org.openidentityplatform.openam.mcp.server.service; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openidentityplatform.openam.mcp.server.model.AuthChainDTO; +import org.openidentityplatform.openam.mcp.server.model.ModuleDTO; +import org.openidentityplatform.openam.mcp.server.model.SearchResponseDTO; +import org.springframework.core.ParameterizedTypeReference; + +import java.io.IOException; +import java.io.InputStream; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +class AuthenticationConfigServiceTest extends OpenAMServiceTest { + + AuthenticationConfigService authenticationConfigService = null; + + @Override + @BeforeEach + public void setupMocks() { + super.setupMocks(); + authenticationConfigService = spy(new AuthenticationConfigService(restClient, openAMConfig)); + } + + @Test + void getOpenAMAuthChains() throws IOException { + InputStream chainsResponseStream = getClass().getClassLoader().getResourceAsStream("auth/chains-response.json"); + SearchResponseDTO chainsResponse = objectMapper.readValue(chainsResponseStream, new TypeReference<>() {}); + doReturn(chainsResponse.result()).when(authenticationConfigService).getRealmAuthChains(anyString(), anyString()); + + InputStream modulesResponseStream = getClass().getClassLoader().getResourceAsStream("auth/modules-response.json"); + SearchResponseDTO modulesResponse = objectMapper.readValue(modulesResponseStream, new TypeReference<>() {}); + doReturn(modulesResponse.result()).when(authenticationConfigService).getRealmAuthModules(anyString(), anyString()); + when(responseSpec.body(eq(new ParameterizedTypeReference>() {}))).thenReturn(chainsResponse); + authenticationConfigService.getOpenAMAuthChains(null); + //assertEquals(realmList.size(), 2); + } +} \ No newline at end of file diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/OpenAMServiceTest.java b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/OpenAMServiceTest.java new file mode 100644 index 0000000000..0416bf2cf0 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/OpenAMServiceTest.java @@ -0,0 +1,69 @@ +package org.openidentityplatform.openam.mcp.server.service; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.client.RestClient; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public abstract class OpenAMServiceTest { + protected final ObjectMapper objectMapper; + + protected final OpenAMConfig openAMConfig; + + public OpenAMServiceTest() { + this.objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + this.openAMConfig = new OpenAMConfig( + null, + false, + null, + null, + "iPlanetDirectoryPro", + null, + null); + } + + + RestClient restClient; + RestClient.RequestHeadersUriSpec requestHeadersUriSpec; + RestClient.RequestBodyUriSpec requestBodyUriSpec; + RestClient.RequestBodySpec requestBodySpec; + RestClient.ResponseSpec responseSpec; + + @BeforeEach + public void setupMocks() { + MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + mockRequest.setAttribute("tokenId", "test-token-id"); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(mockRequest)); + + restClient = mock(RestClient.class); + requestHeadersUriSpec = mock(RestClient.RequestBodyUriSpec.class); + requestBodyUriSpec = mock(RestClient.RequestBodyUriSpec.class); + requestBodySpec = mock(RestClient.RequestBodySpec.class); + responseSpec = mock(RestClient.ResponseSpec.class); + + + when(restClient.get()).thenReturn(requestHeadersUriSpec); + when(requestHeadersUriSpec.uri(anyString())).thenReturn(requestBodySpec); + when(requestBodySpec.header(anyString(), anyString())).thenReturn(requestBodySpec); + when(requestBodySpec.retrieve()).thenReturn(responseSpec); + + when(restClient.put()).thenReturn(requestBodyUriSpec); + when(requestBodyUriSpec.uri(anyString())).thenReturn(requestBodyUriSpec); + when(requestBodyUriSpec.body(anyMap())).thenReturn(requestBodyUriSpec); + when(requestBodyUriSpec.header(anyString(), anyString())).thenReturn(requestBodyUriSpec); + when(requestBodyUriSpec.retrieve()).thenReturn(responseSpec); + + when(restClient.delete()).thenReturn(requestHeadersUriSpec); + } +} diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java new file mode 100644 index 0000000000..db4a3f0cc5 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java @@ -0,0 +1,40 @@ +package org.openidentityplatform.openam.mcp.server.service; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openidentityplatform.openam.mcp.server.model.Realm; +import org.openidentityplatform.openam.mcp.server.model.RealmDTO; +import org.openidentityplatform.openam.mcp.server.model.SearchResponseDTO; +import org.springframework.core.ParameterizedTypeReference; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +class RealmServiceTest extends OpenAMServiceTest { + + RealmService realmService = null; + + @Override + @BeforeEach + public void setupMocks() { + super.setupMocks(); + realmService = new RealmService(restClient, openAMConfig); + } + + @Test + void getRealms() throws IOException { + InputStream is = getClass().getClassLoader().getResourceAsStream("realms/realms-response.json"); + SearchResponseDTO realmsResponse = objectMapper.readValue(is, new TypeReference<>() {}); + + when(responseSpec.body(eq(new ParameterizedTypeReference>() {}))).thenReturn(realmsResponse); + + List realmList = realmService.getRealms(); + assertEquals(realmList.size(), 2); + } +} \ No newline at end of file diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java index d1db7a4de7..5afd4efc64 100644 --- a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java +++ b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java @@ -1,18 +1,12 @@ package org.openidentityplatform.openam.mcp.server.service; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.type.TypeReference; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; +import org.openidentityplatform.openam.mcp.server.model.SearchResponseDTO; import org.openidentityplatform.openam.mcp.server.model.User; import org.openidentityplatform.openam.mcp.server.model.UserDTO; -import org.openidentityplatform.openam.mcp.server.model.UserSearchResponse; import org.springframework.core.ParameterizedTypeReference; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.web.client.RestClient; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; import java.io.IOException; import java.io.InputStream; @@ -21,63 +15,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class UserServiceTest { - - private final ObjectMapper objectMapper; - - public UserServiceTest() { - this.objectMapper = new ObjectMapper(); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - } +class UserServiceTest extends OpenAMServiceTest { UserService userService = null; - RestClient restClient; - RestClient.RequestHeadersUriSpec requestHeadersUriSpec; - RestClient.RequestBodyUriSpec requestBodyUriSpec; - RestClient.RequestBodySpec requestBodySpec; - RestClient.ResponseSpec responseSpec; - @BeforeEach + @BeforeEach() + @Override public void setupMocks() { - MockHttpServletRequest mockRequest = new MockHttpServletRequest(); - mockRequest.setAttribute("tokenId", "test-token-id"); - RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(mockRequest)); - - restClient = mock(RestClient.class); - requestHeadersUriSpec = mock(RestClient.RequestBodyUriSpec.class); - requestBodyUriSpec = mock(RestClient.RequestBodyUriSpec.class); - requestBodySpec = mock(RestClient.RequestBodySpec.class); - responseSpec = mock(RestClient.ResponseSpec.class); - - - when(restClient.get()).thenReturn(requestHeadersUriSpec); - when(requestHeadersUriSpec.uri(anyString())).thenReturn(requestBodySpec); - when(requestBodySpec.header(anyString(), anyString())).thenReturn(requestBodySpec); - when(requestBodySpec.retrieve()).thenReturn(responseSpec); - - when(restClient.put()).thenReturn(requestBodyUriSpec); - when(requestBodyUriSpec.uri(anyString())).thenReturn(requestBodyUriSpec); - when(requestBodyUriSpec.body(anyMap())).thenReturn(requestBodyUriSpec); - when(requestBodyUriSpec.header(anyString(), anyString())).thenReturn(requestBodyUriSpec); - when(requestBodyUriSpec.retrieve()).thenReturn(responseSpec); - - when(restClient.delete()).thenReturn(requestHeadersUriSpec); - - userService = new UserService(restClient, new OpenAMConfig( - null, - false, - null, - null, - "iPlanetDirectoryPro", - null, - null - )); + super.setupMocks(); + userService = new UserService(restClient, openAMConfig); } @@ -85,9 +34,8 @@ public void setupMocks() { void getUsersTest() throws Exception { InputStream is = getClass().getClassLoader().getResourceAsStream("users/users-list-response.json"); - UserSearchResponse userSearchResponse = objectMapper.readValue(is, UserSearchResponse.class); - - when(responseSpec.body(UserSearchResponse.class)).thenReturn(userSearchResponse); + SearchResponseDTO userSearchResponse = objectMapper.readValue(is, new TypeReference<>() {}); + when(responseSpec.body(eq(new ParameterizedTypeReference>() {}))).thenReturn(userSearchResponse); List userList = userService.getUsers(null, null); assertEquals(userList.size(), 2); diff --git a/openam-tools/openam-mcp-server/src/test/resources/auth/all-modules-response.json b/openam-tools/openam-mcp-server/src/test/resources/auth/all-modules-response.json new file mode 100644 index 0000000000..40da120960 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/test/resources/auth/all-modules-response.json @@ -0,0 +1,174 @@ +{ + "result": [ + { + "_id": "httpbasic", + "name": "HTTP Basic", + "collection": false + }, + { + "_id": "activedirectory", + "name": "Active Directory", + "collection": false + }, + { + "_id": "adaptiverisk", + "name": "Adaptive Risk ", + "collection": false + }, + { + "_id": "anonymous", + "name": "Anonymous", + "collection": false + }, + { + "_id": "certificate", + "name": "Certificate", + "collection": false + }, + { + "_id": "datastore", + "name": "Data Store", + "collection": false + }, + { + "_id": "persistentcookie", + "name": "Persistent Cookie", + "collection": false + }, + { + "_id": "jdbc", + "name": "JDBC", + "collection": false + }, + { + "_id": "ldap", + "name": "LDAP", + "collection": false + }, + { + "_id": "msisdn", + "name": "MSISDN", + "collection": false + }, + { + "_id": "membership", + "name": "Membership", + "collection": false + }, + { + "_id": "windowsnt", + "name": "Windows NT", + "collection": false + }, + { + "_id": "oauth2", + "name": "OAuth 2.0 / OpenID Connect", + "collection": false + }, + { + "_id": "windowsdesktopsso", + "name": "Windows Desktop SSO", + "collection": false + }, + { + "_id": "openidconnect", + "name": "OpenID Connect id_token bearer", + "collection": false + }, + { + "_id": "radius", + "name": "RADIUS", + "collection": false + }, + { + "_id": "hotp", + "name": "HOTP", + "collection": false + }, + { + "_id": "authenticatoroath", + "name": "Authenticator (OATH)", + "collection": false + }, + { + "_id": "federation", + "name": "Federation", + "collection": false + }, + { + "_id": "sae", + "name": "SAE", + "collection": false + }, + { + "_id": "wssauth", + "name": "sunAMAuthWSSAuthModuleService", + "collection": false + }, + { + "_id": "scripted", + "name": "Scripted Module", + "collection": false + }, + { + "_id": "deviceidmatch", + "name": "Device Id (Match)", + "collection": false + }, + { + "_id": "deviceidsave", + "name": "Device Id (Save)", + "collection": false + }, + { + "_id": "oath", + "name": "OATH", + "collection": false + }, + { + "_id": "authSaml", + "name": "SAML2", + "collection": false + }, + { + "_id": "authPush", + "name": "Authenticator (Push)", + "collection": false + }, + { + "_id": "authPushReg", + "name": "Authenticator (Push) Registration", + "collection": false + }, + { + "_id": "amster", + "name": "ForgeRock Amster", + "collection": false + }, + { + "_id": "recaptcha", + "name": "QR code confirm from other session", + "collection": false + }, + { + "_id": "securid", + "name": "SecurID", + "collection": false + }, + { + "_id": "webauthnreg", + "name": "WebAuthn Registration", + "collection": false + }, + { + "_id": "webauthnauth", + "name": "WebAuthn Authentication", + "collection": false + }, + { + "_id": "sunAMAuthNtlmService", + "name": "NTLM", + "collection": false + } + ] +} \ No newline at end of file diff --git a/openam-tools/openam-mcp-server/src/test/resources/auth/chains-response.json b/openam-tools/openam-mcp-server/src/test/resources/auth/chains-response.json new file mode 100644 index 0000000000..be815e196e --- /dev/null +++ b/openam-tools/openam-mcp-server/src/test/resources/auth/chains-response.json @@ -0,0 +1,97 @@ +{ + "result": [ + { + "loginFailureUrl": [ + "" + ], + "authChainConfiguration": [ + { + "module": "DataStore", + "criteria": "REQUIRED", + "options": {} + } + ], + "loginPostProcessClass": [], + "loginSuccessUrl": [ + "" + ], + "_id": "ldapService", + "_type": { + "_id": "EMPTY", + "name": "Authentication Configuration", + "collection": true + } + }, + { + "loginFailureUrl": [ + "" + ], + "authChainConfiguration": [ + { + "module": "Amster", + "criteria": "REQUIRED", + "options": {} + } + ], + "loginPostProcessClass": [], + "loginSuccessUrl": [ + "" + ], + "_id": "amsterService", + "_type": { + "_id": "EMPTY", + "name": "Authentication Configuration", + "collection": true + } + }, + { + "loginFailureUrl": [ + "" + ], + "authChainConfiguration": [ + { + "module": "oath2", + "criteria": "REQUISITE", + "options": {} + } + ], + "loginPostProcessClass": [], + "loginSuccessUrl": [ + "" + ], + "_id": "oauth", + "_type": { + "_id": "EMPTY", + "name": "Authentication Configuration", + "collection": true + } + }, + { + "loginFailureUrl": [ + "" + ], + "authChainConfiguration": [ + { + "module": "oidc", + "criteria": "REQUISITE", + "options": {} + } + ], + "loginPostProcessClass": [], + "loginSuccessUrl": [ + "" + ], + "_id": "oidc", + "_type": { + "_id": "EMPTY", + "name": "Authentication Configuration", + "collection": true + } + } + ], + "resultCount": 4, + "pagedResultsCookie": null, + "totalPagedResultsPolicy": "NONE", + "totalPagedResults": -1, + "remainingPagedResults": -1 +} \ No newline at end of file diff --git a/openam-tools/openam-mcp-server/src/test/resources/auth/modules-response.json b/openam-tools/openam-mcp-server/src/test/resources/auth/modules-response.json new file mode 100644 index 0000000000..fa634b2ee3 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/test/resources/auth/modules-response.json @@ -0,0 +1,59 @@ +{ + "result": [ + { + "_id": "SAE", + "typeDescription": "SAE", + "type": "sae" + }, + { + "_id": "Amster", + "typeDescription": "ForgeRock Amster", + "type": "amster" + }, + { + "_id": "LDAP", + "typeDescription": "LDAP", + "type": "ldap" + }, + { + "_id": "oath2", + "typeDescription": "Authenticator (OATH)", + "type": "authenticatoroath" + }, + { + "_id": "WSSAuthModule", + "typeDescription": "WSSAuth", + "type": "wssauth" + }, + { + "_id": "oidc", + "typeDescription": "OpenID Connect id_token bearer", + "type": "openidconnect" + }, + { + "_id": "OATH", + "typeDescription": "OATH", + "type": "oath" + }, + { + "_id": "DataStore", + "typeDescription": "Data Store", + "type": "datastore" + }, + { + "_id": "HOTP", + "typeDescription": "HOTP", + "type": "hotp" + }, + { + "_id": "Federation", + "typeDescription": "Federation", + "type": "federation" + } + ], + "resultCount": 10, + "pagedResultsCookie": null, + "totalPagedResultsPolicy": "EXACT", + "totalPagedResults": 10, + "remainingPagedResults": -1 +} \ No newline at end of file diff --git a/openam-tools/openam-mcp-server/src/test/resources/realms/realms-response.json b/openam-tools/openam-mcp-server/src/test/resources/realms/realms-response.json new file mode 100644 index 0000000000..d915a43e1d --- /dev/null +++ b/openam-tools/openam-mcp-server/src/test/resources/realms/realms-response.json @@ -0,0 +1,26 @@ +{ + "result": [ + { + "_id": "Lw", + "parentPath": null, + "active": true, + "name": "/", + "aliases": [ + "openam", + "openam.example.org" + ] + }, + { + "_id": "L3Rlc3Q", + "parentPath": "/", + "active": true, + "name": "test", + "aliases": [] + } + ], + "resultCount": 2, + "pagedResultsCookie": null, + "totalPagedResultsPolicy": "NONE", + "totalPagedResults": -1, + "remainingPagedResults": -1 +} \ No newline at end of file From deeee5cc8b4e21b1cc1a3026a648c65b1746dd53 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Mon, 10 Nov 2025 11:01:40 +0300 Subject: [PATCH 03/14] OpenAM MCP Server: authentication configuration modules --- .../openam/mcp/server/model/AuthModule.java | 5 + .../{ModuleDTO.java => AuthModuleDTO.java} | 2 +- .../mcp/server/model/AuthModuleSchemaDTO.java | 6 + .../openam/mcp/server/model/SchemaDTO.java | 6 - .../service/AuthenticationConfigService.java | 43 +++-- .../AuthenticationConfigServiceTest.java | 37 +++- .../test/resources/auth/chains-response.json | 24 ++- .../auth/module-schema-response.json | 158 ++++++++++++++++++ .../auth/module-settings-response.json | 23 +++ 9 files changed, 277 insertions(+), 27 deletions(-) create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModule.java rename openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/{ModuleDTO.java => AuthModuleDTO.java} (53%) create mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleSchemaDTO.java delete mode 100644 openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SchemaDTO.java create mode 100644 openam-tools/openam-mcp-server/src/test/resources/auth/module-schema-response.json create mode 100644 openam-tools/openam-mcp-server/src/test/resources/auth/module-settings-response.json diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModule.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModule.java new file mode 100644 index 0000000000..aad4490b4b --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModule.java @@ -0,0 +1,5 @@ +package org.openidentityplatform.openam.mcp.server.model; + +import java.util.Map; + +public record AuthModule(String name, Map settings){} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/ModuleDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleDTO.java similarity index 53% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/ModuleDTO.java rename to openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleDTO.java index 922b928137..da9c603e76 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/ModuleDTO.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleDTO.java @@ -2,4 +2,4 @@ import com.fasterxml.jackson.annotation.JsonProperty; -public record ModuleDTO(@JsonProperty("_id") String id, String typeDescription, String type) {} +public record AuthModuleDTO(@JsonProperty("_id") String id, String typeDescription, String type) {} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleSchemaDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleSchemaDTO.java new file mode 100644 index 0000000000..e97093e739 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleSchemaDTO.java @@ -0,0 +1,6 @@ +package org.openidentityplatform.openam.mcp.server.model; + +import java.util.Map; + +public record AuthModuleSchemaDTO(String type, Map properties) { +} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SchemaDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SchemaDTO.java deleted file mode 100644 index 79a67b10e1..0000000000 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SchemaDTO.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.openidentityplatform.openam.mcp.server.model; - -import java.util.Map; - -public record SchemaDTO(String type, Map properties) { -} diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java index 8de06bd526..530fda9f93 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java @@ -3,11 +3,12 @@ import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; import org.openidentityplatform.openam.mcp.server.model.AuthChain; import org.openidentityplatform.openam.mcp.server.model.AuthChainDTO; +import org.openidentityplatform.openam.mcp.server.model.AuthModule; import org.openidentityplatform.openam.mcp.server.model.CoreAuthModule; import org.openidentityplatform.openam.mcp.server.model.CoreAuthModuleDTO; -import org.openidentityplatform.openam.mcp.server.model.ModuleDTO; +import org.openidentityplatform.openam.mcp.server.model.AuthModuleDTO; import org.openidentityplatform.openam.mcp.server.model.PropertySchemaDTO; -import org.openidentityplatform.openam.mcp.server.model.SchemaDTO; +import org.openidentityplatform.openam.mcp.server.model.AuthModuleSchemaDTO; import org.openidentityplatform.openam.mcp.server.model.SearchResponseDTO; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; @@ -15,6 +16,8 @@ import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -26,16 +29,26 @@ public AuthenticationConfigService(RestClient openAMRestClient, OpenAMConfig ope } @Tool(name = "get_auth_modules", description = "Returns OpenAM authentication modules list") - public void getAuthModules(@ToolParam(required = false, description = "If not set, uses root realm") String realm) { + public List getAuthModules(@ToolParam(required = false, description = "If not set, uses root realm") String realm) { realm = getRealmOrDefault(realm); String tokenId = getTokenId(); - List realmAuthModules = getRealmAuthModules(realm, tokenId); - for(ModuleDTO moduleDTO : realmAuthModules) { - Map schema = getModuleSchema(tokenId, realm, moduleDTO.type()); - Map settings = getModuleSettings(tokenId, realm, moduleDTO.id(), moduleDTO.type()); + List realmAuthModules = getRealmAuthModules(realm, tokenId); + List authModules = new ArrayList<>(); + for(AuthModuleDTO authModuleDTO : realmAuthModules) { + Map schema = getModuleSchema(tokenId, realm, authModuleDTO.type()); + Map settings = getModuleSettings(tokenId, realm, authModuleDTO.id(), authModuleDTO.type()); + + var moduleSettings = new HashMap(); + for (var prop : schema.entrySet()) { + var setting = settings.get(prop.getKey()); + var settingName = prop.getValue().title(); + moduleSettings.put(settingName, setting); + } + var authModuleId = authModuleDTO.typeDescription(); + authModules.add(new AuthModule(authModuleId, moduleSettings)); } - + return authModules; } @Tool(name = "get_auth_chains", description = "Returns OpenAM authentication chains with modules") @@ -44,12 +57,12 @@ public List getOpenAMAuthChains(@ToolParam(required = false, descript String tokenId = getTokenId(); final List chainsDTOList = getRealmAuthChains(realm, tokenId); - final List moduleDTOList = getRealmAuthModules(realm, tokenId); + final List authModuleDTOList = getRealmAuthModules(realm, tokenId); List chains = chainsDTOList.stream().map(chainDTO -> { List chainModules = chainDTO.modules().stream().map(AuthChainDTO.AuthChainModuleDTO::module).toList(); List moduleNames = chainModules.stream() - .map(cm -> moduleDTOList.stream() + .map(cm -> authModuleDTOList.stream() .filter(rm -> cm.equals(rm.id())).findFirst().get().typeDescription()).toList(); return new AuthChain(chainDTO.id(), moduleNames); }).collect(Collectors.toList()); @@ -78,9 +91,9 @@ List getRealmAuthChains(String realm, String tokenId) { return authChainsResponse.result(); } - List getRealmAuthModules(String realm, String tokenId) { + List getRealmAuthModules(String realm, String tokenId) { final String modulesUri = String.format("/json/realms/%s/realm-config/authentication/modules?_queryFilter=true", realm); - SearchResponseDTO modulesResponse = openAMRestClient.get().uri(modulesUri) + SearchResponseDTO modulesResponse = openAMRestClient.get().uri(modulesUri) .header(openAMConfig.tokenHeader(), tokenId) .retrieve() .body(new ParameterizedTypeReference<>() { @@ -91,12 +104,12 @@ List getRealmAuthModules(String realm, String tokenId) { Map getModuleSchema(String tokenId, String realm, String moduleType) { String schemaUrl = String.format( "/json/realms/%s/realm-config/authentication/modules/%s?_action=schema", realm, moduleType); - SchemaDTO schemaDTO = openAMRestClient.post().uri(schemaUrl) + AuthModuleSchemaDTO authModuleSchemaDTO = openAMRestClient.post().uri(schemaUrl) .header(openAMConfig.tokenHeader(), tokenId) .retrieve() - .body(SchemaDTO.class); + .body(AuthModuleSchemaDTO.class); - return schemaDTO.properties(); + return authModuleSchemaDTO.properties(); } Map getModuleSettings(String tokenId, String realm, String moduleId, String moduleType) { diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java index 074d975caa..368e39462a 100644 --- a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java +++ b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java @@ -3,14 +3,20 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.openidentityplatform.openam.mcp.server.model.AuthChain; import org.openidentityplatform.openam.mcp.server.model.AuthChainDTO; -import org.openidentityplatform.openam.mcp.server.model.ModuleDTO; +import org.openidentityplatform.openam.mcp.server.model.AuthModule; +import org.openidentityplatform.openam.mcp.server.model.AuthModuleDTO; +import org.openidentityplatform.openam.mcp.server.model.AuthModuleSchemaDTO; import org.openidentityplatform.openam.mcp.server.model.SearchResponseDTO; import org.springframework.core.ParameterizedTypeReference; import java.io.IOException; import java.io.InputStream; +import java.util.List; +import java.util.Map; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; @@ -35,10 +41,33 @@ void getOpenAMAuthChains() throws IOException { doReturn(chainsResponse.result()).when(authenticationConfigService).getRealmAuthChains(anyString(), anyString()); InputStream modulesResponseStream = getClass().getClassLoader().getResourceAsStream("auth/modules-response.json"); - SearchResponseDTO modulesResponse = objectMapper.readValue(modulesResponseStream, new TypeReference<>() {}); + SearchResponseDTO modulesResponse = objectMapper.readValue(modulesResponseStream, new TypeReference<>() {}); doReturn(modulesResponse.result()).when(authenticationConfigService).getRealmAuthModules(anyString(), anyString()); when(responseSpec.body(eq(new ParameterizedTypeReference>() {}))).thenReturn(chainsResponse); - authenticationConfigService.getOpenAMAuthChains(null); - //assertEquals(realmList.size(), 2); + List authChains = authenticationConfigService.getOpenAMAuthChains(null); + + assertTrue(authChains.size() > 0); + + } + + @Test + void getAuthModules() throws IOException { + InputStream modulesResponseStream = getClass().getClassLoader().getResourceAsStream("auth/modules-response.json"); + SearchResponseDTO modulesResponse = objectMapper.readValue(modulesResponseStream, new TypeReference<>() {}); + doReturn(modulesResponse.result()).when(authenticationConfigService).getRealmAuthModules(anyString(), anyString()); + + InputStream moduleSchemaStream = getClass().getClassLoader().getResourceAsStream("auth/module-schema-response.json"); + AuthModuleSchemaDTO authModuleSchemaDto = objectMapper.readValue(moduleSchemaStream, new TypeReference<>() {}); + + doReturn(authModuleSchemaDto.properties()).when(authenticationConfigService).getModuleSchema(anyString(), anyString(), anyString()); + + InputStream moduleSettingsStream = getClass().getClassLoader().getResourceAsStream("auth/module-settings-response.json"); + Map moduleSettings = objectMapper.readValue(moduleSettingsStream, new TypeReference<>() {}); + + doReturn(moduleSettings).when(authenticationConfigService).getModuleSettings(anyString(), anyString(), anyString(), anyString()); + + List authModules = authenticationConfigService.getAuthModules(null); + assertTrue(authModules.size() > 0); + } } \ No newline at end of file diff --git a/openam-tools/openam-mcp-server/src/test/resources/auth/chains-response.json b/openam-tools/openam-mcp-server/src/test/resources/auth/chains-response.json index be815e196e..81a2b5683e 100644 --- a/openam-tools/openam-mcp-server/src/test/resources/auth/chains-response.json +++ b/openam-tools/openam-mcp-server/src/test/resources/auth/chains-response.json @@ -87,9 +87,31 @@ "name": "Authentication Configuration", "collection": true } + }, + { + "loginFailureUrl": [ + "" + ], + "authChainConfiguration": [ + { + "module": "HOTP", + "criteria": "REQUISITE", + "options": {} + } + ], + "loginPostProcessClass": [], + "loginSuccessUrl": [ + "" + ], + "_id": "hotp", + "_type": { + "_id": "EMPTY", + "name": "Authentication Configuration", + "collection": true + } } ], - "resultCount": 4, + "resultCount": 5, "pagedResultsCookie": null, "totalPagedResultsPolicy": "NONE", "totalPagedResults": -1, diff --git a/openam-tools/openam-mcp-server/src/test/resources/auth/module-schema-response.json b/openam-tools/openam-mcp-server/src/test/resources/auth/module-schema-response.json new file mode 100644 index 0000000000..e3f489cb25 --- /dev/null +++ b/openam-tools/openam-mcp-server/src/test/resources/auth/module-schema-response.json @@ -0,0 +1,158 @@ +{ + "type": "object", + "properties": { + "userProfileEmailAttribute": { + "title": "Email Attribute Name", + "description": "This is the attribute name used by the OTP to email the user", + "propertyOrder": 1400, + "required": true, + "type": "string", + "exampleValue": "" + }, + "smtpFromAddress": { + "title": "Email From Address", + "description": "Emails from the HOTP Authentication module will come from this address.", + "propertyOrder": 800, + "required": true, + "type": "string", + "exampleValue": "" + }, + "authenticationLevel": { + "title": "Authentication Level", + "description": "The authentication level associated with this module.

Each authentication module has an authentication level that can be used to indicate the level of security associated with the module; 0 is the lowest (and the default).", + "propertyOrder": 100, + "required": true, + "type": "integer", + "exampleValue": "" + }, + "mobileCarrierAttribute": { + "title": "Mobile Carrier Attribute Name", + "description": "This is the attribute name used for a mobile carrier domain for sending SMS messages", + "propertyOrder": 1300, + "required": true, + "type": "string", + "exampleValue": "" + }, + "otpValidityDuration": { + "title": "One Time Password Validity Length", + "description": "This One Time Password will remain valid for this period (in minutes)", + "propertyOrder": 900, + "required": true, + "type": "integer", + "exampleValue": "" + }, + "userProfileTelephoneAttribute": { + "title": "Mobile Phone Number Attribute Name", + "description": "This is the attribute name used for a requested text message", + "propertyOrder": 1200, + "required": true, + "type": "string", + "exampleValue": "" + }, + "autoSendOTP": { + "title": "Auto Send OTP Code", + "description": "Select this checkbox if the OTP should be sent automatically", + "propertyOrder": 1500, + "required": true, + "type": "boolean", + "exampleValue": "" + }, + "smtpUsername": { + "title": "Mail Server Authentication Username", + "description": "The username to use if the mail server is using SMTP authentication", + "propertyOrder": 500, + "required": true, + "type": "string", + "exampleValue": "" + }, + "otpLength": { + "title": "One Time Password Length ", + "description": "The length of the generated One Time Password (in digits)", + "propertyOrder": 1000, + "required": true, + "enum": [ + "6", + "8" + ], + "options": { + "enum_titles": [ + "6", + "8" + ] + }, + "type": "string", + "exampleValue": "" + }, + "smsGatewayClass": { + "title": "SMS Gateway Implementation Class", + "description": "The HOTP authentication module uses this class to send SMS messages.

The SMS gateway class must implement the following interface

com.sun.identity.authentication.modules.hotp.SMSGateway", + "propertyOrder": 200, + "required": true, + "type": "string", + "exampleValue": "" + }, + "otpDeliveryMethod": { + "title": "One Time Password Delivery", + "description": "The mechanism used to deliver the One Time Password", + "propertyOrder": 1100, + "required": true, + "enum": [ + "SMS", + "E-mail", + "SMS and E-mail" + ], + "options": { + "enum_titles": [ + "SMS", + "E-mail", + "SMS and E-mail" + ] + }, + "type": "string", + "exampleValue": "" + }, + "smtpSslEnabled": { + "title": "Mail Server Secure Connection ", + "description": "This setting controls whether the authentication module communicates with the mail server using SSL/TLS", + "propertyOrder": 700, + "required": true, + "enum": [ + "SSL", + "Non SSL" + ], + "options": { + "enum_titles": [ + "SSL", + "Non SSL" + ] + }, + "type": "string", + "exampleValue": "" + }, + "smtpHostname": { + "title": "Mail Server Host Name", + "description": "The name of the mail server; OpenAM will use SMTP to send the messages.", + "propertyOrder": 300, + "required": true, + "type": "string", + "exampleValue": "" + }, + "smtpUserPassword": { + "title": "Mail Server Authentication Password", + "description": "The password to use if the mail server is using SMTP authentication", + "propertyOrder": 600, + "required": true, + "type": "string", + "format": "password", + "exampleValue": "" + }, + "smtpHostPort": { + "title": "Mail Server Host Port", + "description": "The port of the mail server.

The default port for SMTP is 25, if using SSL the default port is 465.", + "propertyOrder": 400, + "required": true, + "type": "integer", + "exampleValue": "" + } + } +} \ No newline at end of file diff --git a/openam-tools/openam-mcp-server/src/test/resources/auth/module-settings-response.json b/openam-tools/openam-mcp-server/src/test/resources/auth/module-settings-response.json new file mode 100644 index 0000000000..e2a842739f --- /dev/null +++ b/openam-tools/openam-mcp-server/src/test/resources/auth/module-settings-response.json @@ -0,0 +1,23 @@ +{ + "smtpHostPort": 465, + "smtpSslEnabled": "SSL", + "autoSendOTP": false, + "userProfileEmailAttribute": "mail", + "smtpUsername": "opensso.sun", + "otpDeliveryMethod": "SMS and E-mail", + "smsGatewayClass": "com.sun.identity.authentication.modules.hotp.DefaultSMSGatewayImpl", + "otpLength": "8", + "smtpHostname": "smtp.gmail.com", + "authenticationLevel": 0, + "mobileCarrierAttribute": "", + "otpValidityDuration": 5, + "userProfileTelephoneAttribute": "telephoneNumber", + "smtpFromAddress": "no-reply@openam.org", + "smtpUserPassword": null, + "_id": "hotp", + "_type": { + "_id": "hotp", + "name": "HOTP", + "collection": true + } +} \ No newline at end of file From 6c242f6bd1021bcd40a0c24d2966836e66b5a89c Mon Sep 17 00:00:00 2001 From: maximthomas Date: Mon, 10 Nov 2025 14:16:59 +0300 Subject: [PATCH 04/14] OpenAM MCP Server: update Spring AI, add copyright --- openam-tools/openam-mcp-server/README.md | 129 ++++++++++++++---- openam-tools/openam-mcp-server/pom.xml | 38 +++++- .../server/OpenAmMcpServerApplication.java | 24 +++- .../mcp/server/config/OpenAMConfig.java | 16 +++ .../openam/mcp/server/config/WebConfig.java | 16 +++ .../server/controller/OAuth2Controller.java | 16 +++ .../openam/mcp/server/model/AuthChain.java | 16 +++ .../openam/mcp/server/model/AuthChainDTO.java | 16 +++ .../openam/mcp/server/model/AuthModule.java | 16 +++ .../mcp/server/model/AuthModuleDTO.java | 16 +++ .../mcp/server/model/AuthModuleSchemaDTO.java | 16 +++ .../mcp/server/model/CoreAuthModule.java | 16 +++ .../mcp/server/model/CoreAuthModuleDTO.java | 16 +++ .../mcp/server/model/PropertySchemaDTO.java | 16 +++ .../openam/mcp/server/model/Realm.java | 16 +++ .../openam/mcp/server/model/RealmDTO.java | 17 ++- .../mcp/server/model/SearchResponseDTO.java | 16 +++ .../openam/mcp/server/model/User.java | 16 +++ .../openam/mcp/server/model/UserDTO.java | 16 +++ .../mcp/server/security/AuthInterceptor.java | 16 +++ .../service/AuthenticationConfigService.java | 22 ++- .../server/service/OpenAMAbstractService.java | 16 +++ .../mcp/server/service/RealmService.java | 20 ++- .../mcp/server/service/UserService.java | 16 +++ .../src/main/resources/application.yml | 2 +- .../OpenAmMcpServerApplicationTests.java | 16 +++ .../AuthenticationConfigServiceTest.java | 16 +++ .../mcp/server/service/OpenAMServiceTest.java | 16 +++ .../mcp/server/service/RealmServiceTest.java | 16 +++ .../mcp/server/service/UserServiceTest.java | 16 +++ 30 files changed, 581 insertions(+), 39 deletions(-) diff --git a/openam-tools/openam-mcp-server/README.md b/openam-tools/openam-mcp-server/README.md index 301daa000d..4c48d9e69d 100644 --- a/openam-tools/openam-mcp-server/README.md +++ b/openam-tools/openam-mcp-server/README.md @@ -82,6 +82,30 @@ Set the MCP server environment variable `OPENAM_USE_OAUTH` to `true` ## Available MCP Server Tools +### Authentication Service Tools + +```java +@Tool(name = "get_auth_modules", description = "Returns OpenAM authentication modules list") +public List getAuthModules(@ToolParam(required = false, description = "If not set, uses root realm") String realm) + +@Tool(name = "get_auth_chains", description = "Returns OpenAM authentication chains with modules") +public List getOpenAMAuthChains(@ToolParam(required = false, description = "If not set, uses root realm") String realm) + +@Tool(name = "get_available_modules", description = "Returns all avialable authenticaion modules") +public List getAvailableModuleList() + + +``` + +### Realm Tools + +```java +@Tool(name = "get_realms", description = "Returns OpenAM realm list") +public List getRealms() +``` + +### User Tools + ```java @Tool(name = "get_users", description = "Returns OpenAM user list from the default (root) realm") public List getUsers(@ToolParam(required = false, description = "If not set, uses root realm") String realm, @@ -124,22 +148,22 @@ In JSON-RPC format: "inputSchema": { "type": "object", "properties": { - "realm": { + "arg0": { "type": "string", "description": "If not set, uses root realm" }, - "username": { + "arg1": { "type": "string", "description": "username" }, - "password": { + "arg2": { "type": "string", "description": "user password" } }, "required": [ - "username", - "password" + "arg1", + "arg2" ], "additionalProperties": false } @@ -150,42 +174,52 @@ In JSON-RPC format: "inputSchema": { "type": "object", "properties": { - "realm": { + "arg0": { "type": "string", "description": "If not set, uses root realm" }, - "username": { + "arg1": { "type": "string", "description": "username" }, - "attribute": { + "arg2": { "type": "string", "description": "user attribute name" }, - "value": { + "arg3": { "type": "string", "description": "user attribute value" } }, "required": [ - "username", - "attribute", - "value" + "arg1", + "arg2", + "arg3" ], "additionalProperties": false } }, + { + "name": "get_realms", + "description": "Returns OpenAM realm list", + "inputSchema": { + "type": "object", + "properties": {}, + "required": [], + "additionalProperties": false + } + }, { "name": "get_users", "description": "Returns OpenAM user list from the default (root) realm", "inputSchema": { "type": "object", "properties": { - "realm": { + "arg0": { "type": "string", "description": "If not set, uses root realm" }, - "filter": { + "arg1": { "type": "string", "description": "Username filter" } @@ -194,48 +228,63 @@ In JSON-RPC format: "additionalProperties": false } }, + { + "name": "get_auth_modules", + "description": "Returns OpenAM authentication modules list", + "inputSchema": { + "type": "object", + "properties": { + "arg0": { + "type": "string", + "description": "If not set, uses root realm" + } + }, + "required": [], + "additionalProperties": false + } + }, { "name": "create_user", "description": "Creates a new user", "inputSchema": { "type": "object", "properties": { - "realm": { + "arg0": { "type": "string", "description": "If not set, uses root realm" }, - "userName": { + "arg1": { "type": "string", "description": "Username (login)" }, - "password": { + "arg2": { "type": "string", "description": "Password (min length 8)" }, - "familyName": { + "arg3": { "type": "string", "description": "User family name" }, - "givenName": { + "arg4": { "type": "string", "description": "User given name" }, - "name": { + "arg5": { "type": "string", "description": "Name" }, - "mail": { + "arg6": { "type": "string", "description": "Email" }, - "phone": { + "arg7": { "type": "string", "description": "Phone number" } }, "required": [ - "userName", - "password" + "arg1", + "arg2" ], "additionalProperties": false } @@ -246,22 +295,46 @@ In JSON-RPC format: "inputSchema": { "type": "object", "properties": { - "realm": { + "arg0": { "type": "string", "description": "If not set, uses root realm" }, - "username": { + "arg1": { "type": "string", "description": "Username (login)" } }, "required": [ - "username" + "arg1" ], "additionalProperties": false } + }, + { + "name": "get_available_modules", + "description": "Returns all avialable authenticaion modules", + "inputSchema": { + "type": "object", + "properties": {}, + "required": [], + "additionalProperties": false + } + }, + { + "name": "get_auth_chains", + "description": "Returns OpenAM authentication chains with modules", + "inputSchema": { + "type": "object", + "properties": { + "arg0": { + "type": "string", + "description": "If not set, uses root realm" + } + }, + "required": [], + "additionalProperties": false + } } ] } - ``` diff --git a/openam-tools/openam-mcp-server/pom.xml b/openam-tools/openam-mcp-server/pom.xml index 8952d3a738..04cac7c69a 100644 --- a/openam-tools/openam-mcp-server/pom.xml +++ b/openam-tools/openam-mcp-server/pom.xml @@ -1,4 +1,19 @@ + 4.0.0 @@ -7,14 +22,13 @@ openam-tools 16.0.0-SNAPSHOT - org.openidentityplatform.openam openam-mcp-server OpenAM MCP Server MCP server for OpenAM management 17 3.5.6 - 1.1.0-M4 + 1.1.0-RC1 ${java.version} ${java.version} @@ -60,7 +74,27 @@ + + + + ${basedir}/src/main/resources + true + + **/application*.yml + + + + + org.apache.maven.plugins + maven-resources-plugin + + + ^ + + false + + org.springframework.boot spring-boot-maven-plugin diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplication.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplication.java index 30a0649ddb..4558dae9bf 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplication.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplication.java @@ -1,6 +1,24 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server; import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; +import org.openidentityplatform.openam.mcp.server.service.AuthenticationConfigService; +import org.openidentityplatform.openam.mcp.server.service.RealmService; import org.openidentityplatform.openam.mcp.server.service.UserService; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.ai.tool.method.MethodToolCallbackProvider; @@ -25,8 +43,10 @@ public RestClient getOpenAMRestClient(OpenAMConfig openAMConfig) { } @Bean - public ToolCallbackProvider getTools(UserService userService) { - return MethodToolCallbackProvider.builder().toolObjects(userService).build(); + public ToolCallbackProvider getTools(UserService userService, + RealmService realmService, + AuthenticationConfigService configService) { + return MethodToolCallbackProvider.builder().toolObjects(userService, realmService, configService).build(); } } diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/OpenAMConfig.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/OpenAMConfig.java index 5f37ba653b..0c32aa7716 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/OpenAMConfig.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/OpenAMConfig.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.config; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/WebConfig.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/WebConfig.java index 52fe7fa047..6768e605aa 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/WebConfig.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/WebConfig.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.config; import org.openidentityplatform.openam.mcp.server.security.AuthInterceptor; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java index afae79dd30..fc94047240 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.controller; import jakarta.servlet.http.HttpServletRequest; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChain.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChain.java index b17c219779..1dffe4684a 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChain.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChain.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.model; import java.util.List; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChainDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChainDTO.java index 51b54174ef..2912b7915e 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChainDTO.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChainDTO.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.model; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModule.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModule.java index aad4490b4b..45d4d8f295 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModule.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModule.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.model; import java.util.Map; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleDTO.java index da9c603e76..9df81d1083 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleDTO.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleDTO.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.model; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleSchemaDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleSchemaDTO.java index e97093e739..0ff5d5660b 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleSchemaDTO.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleSchemaDTO.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.model; import java.util.Map; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModule.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModule.java index e2464736ef..6f46478948 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModule.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModule.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.model; public record CoreAuthModule(String id, String name) { diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModuleDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModuleDTO.java index fa118bb495..df6cf30e65 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModuleDTO.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModuleDTO.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.model; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/PropertySchemaDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/PropertySchemaDTO.java index 906f880e97..565298f7a8 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/PropertySchemaDTO.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/PropertySchemaDTO.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.model; public record PropertySchemaDTO(String title, String description) {} \ No newline at end of file diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java index 44a79c13bc..a475f4a7d7 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.model; import java.util.ArrayList; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/RealmDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/RealmDTO.java index b1b8744e96..f5732224d3 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/RealmDTO.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/RealmDTO.java @@ -1,9 +1,24 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.model; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; - public record RealmDTO(@JsonProperty("_id") String id, @JsonProperty("parentPath") String parentPath, @JsonProperty("active") boolean active, diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SearchResponseDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SearchResponseDTO.java index 98cb4325a7..693b958e04 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SearchResponseDTO.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SearchResponseDTO.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.model; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/User.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/User.java index 005512cee7..64c699960a 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/User.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/User.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.model; import java.util.List; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserDTO.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserDTO.java index 0eb4d2ddca..b410cc61b0 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserDTO.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserDTO.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.model; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java index beed128f7d..0aedbb2942 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.security; import com.github.benmanes.caffeine.cache.Cache; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java index 530fda9f93..6f17201e65 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.service; import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; @@ -59,18 +75,16 @@ public List getOpenAMAuthChains(@ToolParam(required = false, descript final List chainsDTOList = getRealmAuthChains(realm, tokenId); final List authModuleDTOList = getRealmAuthModules(realm, tokenId); - List chains = chainsDTOList.stream().map(chainDTO -> { + return chainsDTOList.stream().map(chainDTO -> { List chainModules = chainDTO.modules().stream().map(AuthChainDTO.AuthChainModuleDTO::module).toList(); List moduleNames = chainModules.stream() .map(cm -> authModuleDTOList.stream() .filter(rm -> cm.equals(rm.id())).findFirst().get().typeDescription()).toList(); return new AuthChain(chainDTO.id(), moduleNames); }).collect(Collectors.toList()); - - return chains; } - @Tool(name = "get_available_module_list", description = "Returns all avialable authenticaion modules") + @Tool(name = "get_available_modules", description = "Returns all avialable authenticaion modules") public List getAvailableModuleList() { String tokenId = getTokenId(); String coreModulesUri = "/json/global-config/authentication/modules?_action=getAllTypes"; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java index 619572c6b3..70ccc286b2 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.service; import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/RealmService.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/RealmService.java index 3e667447a9..2b2cd27942 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/RealmService.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/RealmService.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.service; import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; @@ -6,17 +22,19 @@ import org.openidentityplatform.openam.mcp.server.model.SearchResponseDTO; import org.springframework.ai.tool.annotation.Tool; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient; import java.util.List; import java.util.stream.Collectors; +@Service public class RealmService extends OpenAMAbstractService { public RealmService(RestClient openAMRestClient, OpenAMConfig openAMConfig) { super(openAMRestClient, openAMConfig); } - @Tool(name = "get_realm", description = "Returns OpenAM realm list") + @Tool(name = "get_realms", description = "Returns OpenAM realm list") public List getRealms() { String uri = "/json/global-config/realms?_queryFilter=true"; String tokenId = getTokenId(); diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java index df79ba87ec..faf5a12549 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.service; import org.openidentityplatform.openam.mcp.server.config.OpenAMConfig; diff --git a/openam-tools/openam-mcp-server/src/main/resources/application.yml b/openam-tools/openam-mcp-server/src/main/resources/application.yml index 9fb20a311c..7ca35d6b87 100644 --- a/openam-tools/openam-mcp-server/src/main/resources/application.yml +++ b/openam-tools/openam-mcp-server/src/main/resources/application.yml @@ -7,7 +7,7 @@ spring: mcp: server: name: openam-mcp-server - version: 0.0.1 + version: ^project.version^ protocol: STATELESS openam: diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplicationTests.java b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplicationTests.java index 39cb4fe150..4d8b6f7df9 100644 --- a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplicationTests.java +++ b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplicationTests.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server; import org.junit.jupiter.api.Test; diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java index 368e39462a..29a9dc661e 100644 --- a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java +++ b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.service; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/OpenAMServiceTest.java b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/OpenAMServiceTest.java index 0416bf2cf0..00144a9a2a 100644 --- a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/OpenAMServiceTest.java +++ b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/OpenAMServiceTest.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.service; import com.fasterxml.jackson.databind.DeserializationFeature; diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java index db4a3f0cc5..de8e70c669 100644 --- a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java +++ b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.service; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java index 5afd4efc64..0ecc19f800 100644 --- a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java +++ b/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java @@ -1,3 +1,19 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2025 3A Systems LLC. + */ + package org.openidentityplatform.openam.mcp.server.service; import com.fasterxml.jackson.core.type.TypeReference; From efd26180bf535f1fb629a6b73cbd421c8f63eb0d Mon Sep 17 00:00:00 2001 From: maximthomas Date: Wed, 12 Nov 2025 09:37:58 +0300 Subject: [PATCH 05/14] Update OpenAM parent version for MCP Server --- openam-tools/openam-mcp-server/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openam-tools/openam-mcp-server/pom.xml b/openam-tools/openam-mcp-server/pom.xml index 04cac7c69a..c048b98b94 100644 --- a/openam-tools/openam-mcp-server/pom.xml +++ b/openam-tools/openam-mcp-server/pom.xml @@ -20,7 +20,7 @@ org.openidentityplatform.openam openam-tools - 16.0.0-SNAPSHOT + 16.0.4-SNAPSHOT openam-mcp-server OpenAM MCP Server From f99534a8541c6ab21a2280ac1d4b3559598dd887 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Wed, 12 Nov 2025 09:54:41 +0300 Subject: [PATCH 06/14] Update README.md --- openam-tools/openam-mcp-server/README.md | 46 +++++++++++-------- .../service/AuthenticationConfigService.java | 2 +- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/openam-tools/openam-mcp-server/README.md b/openam-tools/openam-mcp-server/README.md index 4c48d9e69d..ef9f7326ee 100644 --- a/openam-tools/openam-mcp-server/README.md +++ b/openam-tools/openam-mcp-server/README.md @@ -1,7 +1,6 @@ # OpenAM MCP Server -OpenAM MCP Server is a lightweight management service for OpenAM user accounts. -It allows administrators to create, update, delete, and reset passwords for users. +OpenAM MCP Server is a lightweight management service for OpenAM user accounts. It allows administrators to create, update, delete, and reset passwords for users, as well as retrieve authentication modules and chains configurations. ## Prerequisites * JDK 17+ @@ -17,14 +16,12 @@ export OPENAM_ADMIN_USERNAME=amadmin export OPENAM_ADMIN_PASSWORD=passw0rd ``` -Clone the source code: - -Run from the source code: +Clone and run from source: ```bash mvn spring-boot:run ``` -Build and run: +Or build and run the JAR: ```bash cd openam-mcp-server @@ -34,17 +31,18 @@ mvn package -DskipTests=true && java -jar ./target/openam-mcp-server-*.jar ## Advanced Authentication > [!IMPORTANT] -> Using administrative credentials directly in the MCP server may be insecure, so this server supports OpenAM's OAuth 2.0 protocol. +> UUsing administrative credentials directly in the MCP server can be insecure. This server therefore supports OpenAM's OAuth 2.0 protocol. protocol. -This approach requires additional OpenAM configuration: +This approach requires additional OpenAM configuration. ### OpenAM OAuth2.0 Service Configuration -Go to the OpenAM admin console, select the root realm, then select **Configure OAuth Provider** → **Configure OAuth2.0**. -Leave the settings unchanged and click the Create button. +1. In the OpenAM admin console, select the root realm. +1. Select **Configure OAuth Provider** → **Configure OAuth2.0**. +1. Leave the settings unchanged and click **Create**. -Set the following settings for the OAuth2.0 Provider: +Configure the OAuth 2.0 Provider with the following settings: | Setting | Value | |------------------------------------------------------------------|---------| @@ -54,13 +52,16 @@ Set the following settings for the OAuth2.0 Provider: | OAuth2 Token Signing Algorithm | RS256 | | Allow Open Dynamic Client Registration | enabled | -See more in the OpenAM OAuth2.0 documentation: https://doc.openidentityplatform.org/openam/admin-guide/chap-oauth2 +For more details, see the OpenAM OAuth 2.0 documentation:: https://doc.openidentityplatform.org/openam/admin-guide/chap-oauth2 ### Authentication Chain Configuration -Create the OpenAM OAuth 2.0 authentication chain, so that the MCP server can exchange an access token for an SSO token to manage identities. -In the administrator console, select the root realm. Then in the left menu select **Authentication** → **Modules** and create a new module with name `oidc` and type `OpenID Connect id_token bearer`. -Set the following settings for the `oidc` authentication module: +Create an OpenAM OAuth 2.0 authentication chain so the MCP server can exchange an access token for an SSO token to manage identities. + +1. In the admin console, select the root realm. +1. In the left menu go to **Authentication** → **Modules** and create a new module with named `oidc` of type `OpenID Connect id_token bearer`. + +Configure the `oidc` module as follows: | Setting | Value | |----------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------| @@ -69,16 +70,21 @@ Set the following settings for the `oidc` authentication module: | Mapping of jwt attributes to local LDAP attributes | sub=uid | | Audience name | Your MCP client's client ID, for example openam-mcp-server | -Create an authentication chain with the `oidc` module: +Create an authentication chain: -In the administrator console select the root realm then in the left menu select **Authentication** → **Chains** and create a new chain with name `oidc` and the following settings: +1. In the admin console, select the root realm. +1. In the left menu select **Authentication** → **Chains** +1. Create a new chain named `oidc` with the following configuration: | Module | Criteria | |--------|------------| | oidc | REQUISITE | -Set the MCP server environment variable `OPENAM_USE_OAUTH` to `true` +Finally, enable OAuth 2.0 in the MCP server: +```bash +export OPENAM_USE_OAUTH=true +``` ## Available MCP Server Tools @@ -91,7 +97,7 @@ public List getAuthModules(@ToolParam(required = false, description @Tool(name = "get_auth_chains", description = "Returns OpenAM authentication chains with modules") public List getOpenAMAuthChains(@ToolParam(required = false, description = "If not set, uses root realm") String realm) -@Tool(name = "get_available_modules", description = "Returns all avialable authenticaion modules") +@Tool(name = "get_available_modules", description = "Returns all available authentication modules") public List getAvailableModuleList() @@ -312,7 +318,7 @@ In JSON-RPC format: }, { "name": "get_available_modules", - "description": "Returns all avialable authenticaion modules", + "description": "Returns all available authenticaion modules", "inputSchema": { "type": "object", "properties": {}, diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java index 6f17201e65..82ca46bc65 100644 --- a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java +++ b/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java @@ -84,7 +84,7 @@ public List getOpenAMAuthChains(@ToolParam(required = false, descript }).collect(Collectors.toList()); } - @Tool(name = "get_available_modules", description = "Returns all avialable authenticaion modules") + @Tool(name = "get_available_modules", description = "Returns all available authentication modules") public List getAvailableModuleList() { String tokenId = getTokenId(); String coreModulesUri = "/json/global-config/authentication/modules?_action=getAllTypes"; From 4ea608acc5bfcaedb9afbcefe04e35440d590a5c Mon Sep 17 00:00:00 2001 From: maximthomas Date: Wed, 12 Nov 2025 11:05:11 +0300 Subject: [PATCH 07/14] Move OpenAM MCP server to root project --- .../README.md | 0 .../pom.xml | 2 +- .../server/OpenAmMcpServerApplication.java | 0 .../mcp/server/config/OpenAMConfig.java | 0 .../openam/mcp/server/config/WebConfig.java | 0 .../server/controller/OAuth2Controller.java | 0 .../openam/mcp/server/model/AuthChain.java | 0 .../openam/mcp/server/model/AuthChainDTO.java | 0 .../openam/mcp/server/model/AuthModule.java | 0 .../mcp/server/model/AuthModuleDTO.java | 0 .../mcp/server/model/AuthModuleSchemaDTO.java | 0 .../mcp/server/model/CoreAuthModule.java | 0 .../mcp/server/model/CoreAuthModuleDTO.java | 0 .../mcp/server/model/PropertySchemaDTO.java | 0 .../openam/mcp/server/model/Realm.java | 0 .../openam/mcp/server/model/RealmDTO.java | 0 .../mcp/server/model/SearchResponseDTO.java | 0 .../openam/mcp/server/model/User.java | 0 .../openam/mcp/server/model/UserDTO.java | 0 .../mcp/server/security/AuthInterceptor.java | 0 .../service/AuthenticationConfigService.java | 0 .../server/service/OpenAMAbstractService.java | 0 .../mcp/server/service/RealmService.java | 0 .../mcp/server/service/UserService.java | 0 .../src/main/resources/application.yml | 0 .../OpenAmMcpServerApplicationTests.java | 0 .../AuthenticationConfigServiceTest.java | 0 .../mcp/server/service/OpenAMServiceTest.java | 0 .../mcp/server/service/RealmServiceTest.java | 0 .../mcp/server/service/UserServiceTest.java | 0 .../resources/auth/all-modules-response.json | 0 .../test/resources/auth/chains-response.json | 0 .../auth/module-schema-response.json | 0 .../auth/module-settings-response.json | 0 .../test/resources/auth/modules-response.json | 0 .../resources/realms/realms-response.json | 0 .../test/resources/users/user-response.json | 0 .../resources/users/users-list-response.json | 0 openam-tools/pom.xml | 21 ------------------- pom.xml | 9 ++++++++ 40 files changed, 10 insertions(+), 22 deletions(-) rename {openam-tools/openam-mcp-server => openam-mcp-server}/README.md (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/pom.xml (98%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplication.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/config/OpenAMConfig.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/config/WebConfig.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChain.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChainDTO.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModule.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleDTO.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleSchemaDTO.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModule.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModuleDTO.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/model/PropertySchemaDTO.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/model/RealmDTO.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/model/SearchResponseDTO.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/model/User.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserDTO.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/service/RealmService.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/main/resources/application.yml (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/test/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplicationTests.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/test/java/org/openidentityplatform/openam/mcp/server/service/OpenAMServiceTest.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/test/resources/auth/all-modules-response.json (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/test/resources/auth/chains-response.json (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/test/resources/auth/module-schema-response.json (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/test/resources/auth/module-settings-response.json (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/test/resources/auth/modules-response.json (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/test/resources/realms/realms-response.json (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/test/resources/users/user-response.json (100%) rename {openam-tools/openam-mcp-server => openam-mcp-server}/src/test/resources/users/users-list-response.json (100%) diff --git a/openam-tools/openam-mcp-server/README.md b/openam-mcp-server/README.md similarity index 100% rename from openam-tools/openam-mcp-server/README.md rename to openam-mcp-server/README.md diff --git a/openam-tools/openam-mcp-server/pom.xml b/openam-mcp-server/pom.xml similarity index 98% rename from openam-tools/openam-mcp-server/pom.xml rename to openam-mcp-server/pom.xml index c048b98b94..c5e35d44f9 100644 --- a/openam-tools/openam-mcp-server/pom.xml +++ b/openam-mcp-server/pom.xml @@ -19,7 +19,7 @@ 4.0.0 org.openidentityplatform.openam - openam-tools + openam 16.0.4-SNAPSHOT openam-mcp-server diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplication.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplication.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplication.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplication.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/OpenAMConfig.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/OpenAMConfig.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/OpenAMConfig.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/OpenAMConfig.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/WebConfig.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/WebConfig.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/WebConfig.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/config/WebConfig.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChain.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChain.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChain.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChain.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChainDTO.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChainDTO.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChainDTO.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthChainDTO.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModule.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModule.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModule.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModule.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleDTO.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleDTO.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleDTO.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleDTO.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleSchemaDTO.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleSchemaDTO.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleSchemaDTO.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/AuthModuleSchemaDTO.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModule.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModule.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModule.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModule.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModuleDTO.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModuleDTO.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModuleDTO.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/CoreAuthModuleDTO.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/PropertySchemaDTO.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/PropertySchemaDTO.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/PropertySchemaDTO.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/PropertySchemaDTO.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/RealmDTO.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/RealmDTO.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/RealmDTO.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/RealmDTO.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SearchResponseDTO.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SearchResponseDTO.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SearchResponseDTO.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/SearchResponseDTO.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/User.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/User.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/User.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/User.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserDTO.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserDTO.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserDTO.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/UserDTO.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/RealmService.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/RealmService.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/RealmService.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/RealmService.java diff --git a/openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java similarity index 100% rename from openam-tools/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java rename to openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java diff --git a/openam-tools/openam-mcp-server/src/main/resources/application.yml b/openam-mcp-server/src/main/resources/application.yml similarity index 100% rename from openam-tools/openam-mcp-server/src/main/resources/application.yml rename to openam-mcp-server/src/main/resources/application.yml diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplicationTests.java b/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplicationTests.java similarity index 100% rename from openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplicationTests.java rename to openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/OpenAmMcpServerApplicationTests.java diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java b/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java similarity index 100% rename from openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java rename to openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigServiceTest.java diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/OpenAMServiceTest.java b/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/OpenAMServiceTest.java similarity index 100% rename from openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/OpenAMServiceTest.java rename to openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/OpenAMServiceTest.java diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java b/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java similarity index 100% rename from openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java rename to openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java diff --git a/openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java b/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java similarity index 100% rename from openam-tools/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java rename to openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java diff --git a/openam-tools/openam-mcp-server/src/test/resources/auth/all-modules-response.json b/openam-mcp-server/src/test/resources/auth/all-modules-response.json similarity index 100% rename from openam-tools/openam-mcp-server/src/test/resources/auth/all-modules-response.json rename to openam-mcp-server/src/test/resources/auth/all-modules-response.json diff --git a/openam-tools/openam-mcp-server/src/test/resources/auth/chains-response.json b/openam-mcp-server/src/test/resources/auth/chains-response.json similarity index 100% rename from openam-tools/openam-mcp-server/src/test/resources/auth/chains-response.json rename to openam-mcp-server/src/test/resources/auth/chains-response.json diff --git a/openam-tools/openam-mcp-server/src/test/resources/auth/module-schema-response.json b/openam-mcp-server/src/test/resources/auth/module-schema-response.json similarity index 100% rename from openam-tools/openam-mcp-server/src/test/resources/auth/module-schema-response.json rename to openam-mcp-server/src/test/resources/auth/module-schema-response.json diff --git a/openam-tools/openam-mcp-server/src/test/resources/auth/module-settings-response.json b/openam-mcp-server/src/test/resources/auth/module-settings-response.json similarity index 100% rename from openam-tools/openam-mcp-server/src/test/resources/auth/module-settings-response.json rename to openam-mcp-server/src/test/resources/auth/module-settings-response.json diff --git a/openam-tools/openam-mcp-server/src/test/resources/auth/modules-response.json b/openam-mcp-server/src/test/resources/auth/modules-response.json similarity index 100% rename from openam-tools/openam-mcp-server/src/test/resources/auth/modules-response.json rename to openam-mcp-server/src/test/resources/auth/modules-response.json diff --git a/openam-tools/openam-mcp-server/src/test/resources/realms/realms-response.json b/openam-mcp-server/src/test/resources/realms/realms-response.json similarity index 100% rename from openam-tools/openam-mcp-server/src/test/resources/realms/realms-response.json rename to openam-mcp-server/src/test/resources/realms/realms-response.json diff --git a/openam-tools/openam-mcp-server/src/test/resources/users/user-response.json b/openam-mcp-server/src/test/resources/users/user-response.json similarity index 100% rename from openam-tools/openam-mcp-server/src/test/resources/users/user-response.json rename to openam-mcp-server/src/test/resources/users/user-response.json diff --git a/openam-tools/openam-mcp-server/src/test/resources/users/users-list-response.json b/openam-mcp-server/src/test/resources/users/users-list-response.json similarity index 100% rename from openam-tools/openam-mcp-server/src/test/resources/users/users-list-response.json rename to openam-mcp-server/src/test/resources/users/users-list-response.json diff --git a/openam-tools/pom.xml b/openam-tools/pom.xml index 1c846a9cac..324dc5c6b4 100755 --- a/openam-tools/pom.xml +++ b/openam-tools/pom.xml @@ -93,26 +93,5 @@ - - - jdk17a - - [17,) - - - build-helper-plugin - openam-build-tools - openam-configurator-tool - openam-installtools - openam-installtools-launcher - openam-upgrade-tool - openam-license-core - openam-license-manager-cli - openam-installer-utils - openam-license-servlet - openam-mcp-server - - - diff --git a/pom.xml b/pom.xml index d778d57dc4..d9b42154f4 100644 --- a/pom.xml +++ b/pom.xml @@ -249,6 +249,15 @@ + + jdk17.options + + [17,) + + + openam-mcp-server + + From b6dd003bc7f0c4168c8c9977a67dc33b218a6e9f Mon Sep 17 00:00:00 2001 From: maximthomas Date: Wed, 24 Dec 2025 10:05:30 +0300 Subject: [PATCH 08/14] OpenAM MCP Server: update Spring Boot and Spring AI --- openam-mcp-server/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openam-mcp-server/pom.xml b/openam-mcp-server/pom.xml index c5e35d44f9..e2b4aff3dc 100644 --- a/openam-mcp-server/pom.xml +++ b/openam-mcp-server/pom.xml @@ -27,8 +27,8 @@ MCP server for OpenAM management 17 - 3.5.6 - 1.1.0-RC1 + 4.0.1 + 1.1.2 ${java.version} ${java.version} From 2771dd9ce4fc478870e3a5340469965ac4c108f3 Mon Sep 17 00:00:00 2001 From: Maxim Thomas Date: Fri, 27 Mar 2026 12:30:13 +0300 Subject: [PATCH 09/14] Update openam-mcp-server/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- openam-mcp-server/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openam-mcp-server/README.md b/openam-mcp-server/README.md index ef9f7326ee..c7c0b5b4b9 100644 --- a/openam-mcp-server/README.md +++ b/openam-mcp-server/README.md @@ -31,7 +31,7 @@ mvn package -DskipTests=true && java -jar ./target/openam-mcp-server-*.jar ## Advanced Authentication > [!IMPORTANT] -> UUsing administrative credentials directly in the MCP server can be insecure. This server therefore supports OpenAM's OAuth 2.0 protocol. +> Using administrative credentials directly in the MCP server can be insecure. This server therefore supports OpenAM's OAuth 2.0 protocol. protocol. This approach requires additional OpenAM configuration. From b12dcc8be87e6ca82370ea536d168f75b6ddf5e3 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Fri, 27 Mar 2026 12:38:03 +0300 Subject: [PATCH 10/14] Update parent version for openam-mcp-server --- openam-mcp-server/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openam-mcp-server/pom.xml b/openam-mcp-server/pom.xml index e2b4aff3dc..fca4209302 100644 --- a/openam-mcp-server/pom.xml +++ b/openam-mcp-server/pom.xml @@ -20,7 +20,7 @@ org.openidentityplatform.openam openam - 16.0.4-SNAPSHOT + 16.0.7-SNAPSHOT openam-mcp-server OpenAM MCP Server From 47fee25f6e5058c6b635fb4fd44e77572727dec6 Mon Sep 17 00:00:00 2001 From: Maxim Thomas Date: Fri, 27 Mar 2026 12:52:36 +0300 Subject: [PATCH 11/14] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../openam/mcp/server/controller/OAuth2Controller.java | 8 +++++++- .../openam/mcp/server/model/Realm.java | 8 ++++++-- .../openam/mcp/server/security/AuthInterceptor.java | 4 ++++ .../mcp/server/service/AuthenticationConfigService.java | 6 +++++- .../openam/mcp/server/service/OpenAMAbstractService.java | 5 +++++ .../openam/mcp/server/service/UserService.java | 2 +- .../openam/mcp/server/service/RealmServiceTest.java | 1 + 7 files changed, 29 insertions(+), 5 deletions(-) diff --git a/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java index fc94047240..92db86b0a9 100644 --- a/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java +++ b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/controller/OAuth2Controller.java @@ -41,9 +41,15 @@ public OAuth2Controller(RestClient openAMRestClient) { @GetMapping("/.well-known/**") public ResponseEntity openAMWellKnown(HttpServletRequest request) { + StringBuilder uriBuilder = new StringBuilder("/oauth2").append(request.getRequestURI()); + String queryString = request.getQueryString(); + if (queryString != null && !queryString.isEmpty()) { + uriBuilder.append('?').append(queryString); + } + RestClient.RequestBodySpec requestSpec = openAMRestClient .method(HttpMethod.valueOf(request.getMethod())) - .uri("/oauth2".concat(request.getRequestURI())) + .uri(uriBuilder.toString()) .headers(headers -> request.getHeaderNames().asIterator().forEachRemaining(name -> { if (IGNORE_HEADERS.contains(name.toLowerCase())) { return; diff --git a/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java index a475f4a7d7..5b034d0542 100644 --- a/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java +++ b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java @@ -24,8 +24,12 @@ public record Realm(String name, String parentPath, List aliases) { public Realm(RealmDTO realmDTO) { + List safeAliases = (realmDTO.aliases() == null) + ? new ArrayList<>() + : new ArrayList<>(realmDTO.aliases()); this(realmDTO.name().equals("/") ? "root" : realmDTO.name(), - realmDTO.active(), realmDTO.parentPath(), - new ArrayList<>(realmDTO.aliases())); + realmDTO.active(), + realmDTO.parentPath(), + safeAliases); } } \ No newline at end of file diff --git a/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java index 0aedbb2942..11894b6228 100644 --- a/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java +++ b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/security/AuthInterceptor.java @@ -128,6 +128,10 @@ private boolean preHandleOAuth(HttpServletRequest request, HttpServletResponse r String authHeader = request.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer ")) { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setHeader("WWW-Authenticate", "Bearer realm=\"OpenAM\""); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.getWriter().write("{\"error\":\"Unauthorized\"}"); return false; } String accessToken = authHeader.substring(7); diff --git a/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java index 82ca46bc65..2d6ef480ee 100644 --- a/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java +++ b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/AuthenticationConfigService.java @@ -79,7 +79,11 @@ public List getOpenAMAuthChains(@ToolParam(required = false, descript List chainModules = chainDTO.modules().stream().map(AuthChainDTO.AuthChainModuleDTO::module).toList(); List moduleNames = chainModules.stream() .map(cm -> authModuleDTOList.stream() - .filter(rm -> cm.equals(rm.id())).findFirst().get().typeDescription()).toList(); + .filter(rm -> cm.equals(rm.id())) + .findFirst() + .map(AuthModuleDTO::typeDescription) + .orElse(cm)) + .toList(); return new AuthChain(chainDTO.id(), moduleNames); }).collect(Collectors.toList()); } diff --git a/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java index 70ccc286b2..fd072cde84 100644 --- a/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java +++ b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/OpenAMAbstractService.java @@ -36,6 +36,11 @@ public OpenAMAbstractService(RestClient openAMRestClient, OpenAMConfig openAMCon protected String getTokenId() { RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); + if (attrs == null) { + throw new IllegalStateException( + "No RequestAttributes available. getTokenId() must be called within the context of an HTTP request " + + "and with the appropriate RequestContext infrastructure configured."); + } return (String) attrs.getAttribute("tokenId", RequestAttributes.SCOPE_REQUEST); } diff --git a/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java index faf5a12549..028132ebf0 100644 --- a/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java +++ b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/service/UserService.java @@ -71,7 +71,7 @@ public User setUserAttribute(@ToolParam(required = false, description = "If not realm = getRealmOrDefault(realm); if(!ATTR_MAP.containsKey(attribute)) { - throw new RuntimeException(String.format("invalid attribute: %s; allowed values %s", attribute, ATTR_MAP.keySet())); + throw new IllegalArgumentException(String.format("invalid attribute: %s; allowed values %s", attribute, ATTR_MAP.keySet())); } String tokenId = getTokenId(); diff --git a/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java b/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java index de8e70c669..4b9b182790 100644 --- a/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java +++ b/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java @@ -46,6 +46,7 @@ public void setupMocks() { @Test void getRealms() throws IOException { InputStream is = getClass().getClassLoader().getResourceAsStream("realms/realms-response.json"); + assertNotNull(is, "Test resource 'realms/realms-response.json' not found on the classpath"); SearchResponseDTO realmsResponse = objectMapper.readValue(is, new TypeReference<>() {}); when(responseSpec.body(eq(new ParameterizedTypeReference>() {}))).thenReturn(realmsResponse); From 9e6920ae021eb63aebc468a04a9dc18b24d70472 Mon Sep 17 00:00:00 2001 From: Maxim Thomas Date: Fri, 27 Mar 2026 12:56:28 +0300 Subject: [PATCH 12/14] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- openam-mcp-server/README.md | 5 ++--- .../openam/mcp/server/service/RealmServiceTest.java | 2 +- .../openam/mcp/server/service/UserServiceTest.java | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openam-mcp-server/README.md b/openam-mcp-server/README.md index c7c0b5b4b9..79cc6426b0 100644 --- a/openam-mcp-server/README.md +++ b/openam-mcp-server/README.md @@ -32,7 +32,6 @@ mvn package -DskipTests=true && java -jar ./target/openam-mcp-server-*.jar > [!IMPORTANT] > Using administrative credentials directly in the MCP server can be insecure. This server therefore supports OpenAM's OAuth 2.0 protocol. -protocol. This approach requires additional OpenAM configuration. @@ -52,7 +51,7 @@ Configure the OAuth 2.0 Provider with the following settings: | OAuth2 Token Signing Algorithm | RS256 | | Allow Open Dynamic Client Registration | enabled | -For more details, see the OpenAM OAuth 2.0 documentation:: https://doc.openidentityplatform.org/openam/admin-guide/chap-oauth2 +For more details, see the OpenAM OAuth 2.0 documentation: https://doc.openidentityplatform.org/openam/admin-guide/chap-oauth2 ### Authentication Chain Configuration @@ -318,7 +317,7 @@ In JSON-RPC format: }, { "name": "get_available_modules", - "description": "Returns all available authenticaion modules", + "description": "Returns all available authentication modules", "inputSchema": { "type": "object", "properties": {}, diff --git a/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java b/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java index 4b9b182790..5cc6ba97e4 100644 --- a/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java +++ b/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/RealmServiceTest.java @@ -52,6 +52,6 @@ void getRealms() throws IOException { when(responseSpec.body(eq(new ParameterizedTypeReference>() {}))).thenReturn(realmsResponse); List realmList = realmService.getRealms(); - assertEquals(realmList.size(), 2); + assertEquals(2, realmList.size()); } } \ No newline at end of file diff --git a/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java b/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java index 0ecc19f800..c79ec2d66e 100644 --- a/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java +++ b/openam-mcp-server/src/test/java/org/openidentityplatform/openam/mcp/server/service/UserServiceTest.java @@ -54,7 +54,7 @@ void getUsersTest() throws Exception { when(responseSpec.body(eq(new ParameterizedTypeReference>() {}))).thenReturn(userSearchResponse); List userList = userService.getUsers(null, null); - assertEquals(userList.size(), 2); + assertEquals(2, userList.size()); } @Test From c1ab9e1864e53a088169e5b437cf73337fb1aded Mon Sep 17 00:00:00 2001 From: maximthomas Date: Fri, 27 Mar 2026 14:20:11 +0300 Subject: [PATCH 13/14] fix compilation error --- .../openam/mcp/server/model/Realm.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java index 5b034d0542..0b7e7b9ff5 100644 --- a/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java +++ b/openam-mcp-server/src/main/java/org/openidentityplatform/openam/mcp/server/model/Realm.java @@ -11,7 +11,7 @@ * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions copyright [year] [name of copyright owner]". * - * Copyright 2025 3A Systems LLC. + * Copyright 2025-2026 3A Systems LLC. */ package org.openidentityplatform.openam.mcp.server.model; @@ -24,12 +24,11 @@ public record Realm(String name, String parentPath, List aliases) { public Realm(RealmDTO realmDTO) { - List safeAliases = (realmDTO.aliases() == null) - ? new ArrayList<>() - : new ArrayList<>(realmDTO.aliases()); this(realmDTO.name().equals("/") ? "root" : realmDTO.name(), realmDTO.active(), realmDTO.parentPath(), - safeAliases); + (realmDTO.aliases() == null) + ? new ArrayList<>() + : new ArrayList<>(realmDTO.aliases())); } } \ No newline at end of file From 0a83382fe65560c720e59a6d48944185c4904b7c Mon Sep 17 00:00:00 2001 From: maximthomas Date: Fri, 27 Mar 2026 14:20:43 +0300 Subject: [PATCH 14/14] change default password for the admin user --- openam-mcp-server/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openam-mcp-server/src/main/resources/application.yml b/openam-mcp-server/src/main/resources/application.yml index 7ca35d6b87..abb64ca984 100644 --- a/openam-mcp-server/src/main/resources/application.yml +++ b/openam-mcp-server/src/main/resources/application.yml @@ -15,7 +15,7 @@ openam: tokenHeader: ${OPENAM_TOKEN_HEADER:iPlanetDirectoryPro} useOAuthForAuthentication: ${OPENAM_USE_OAUTH:false} username: ${OPENAM_ADMIN_USERNAME:amadmin} - password: ${OPENAM_ADMIN_PASSWORD:ampassword} + password: ${OPENAM_ADMIN_PASSWORD:changeit} oidcAuthChain: ${OPENAM_OIDC_AUTH_CHAIN:oidc} oidcAuthHeader: ${OPENAM_OIDC_AUTH_HEADER:oidc_id_token}