/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * docs/licenses/cddl.txt * or http://www.opensource.org/licenses/cddl1.php. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * docs/licenses/cddl.txt. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2019-2024 Ping Identity Corporation */ package com.unboundid.directory.sdk.examples; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.unboundid.directory.sdk.broker.api.TokenResourceLookupMethod; import com.unboundid.directory.sdk.broker.config.TokenResourceLookupMethodConfig; import com.unboundid.directory.sdk.broker.types.BrokerContext; import com.unboundid.directory.sdk.broker.types.TokenOwnerPrincipal; import com.unboundid.directory.sdk.common.types.LogSeverity; import com.unboundid.directory.sdk.common.types.TokenValidationResult; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.util.args.Argument; import com.unboundid.util.args.ArgumentException; import com.unboundid.util.args.ArgumentParser; import com.unboundid.util.args.FileArgument; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; /** * This class provides a simple example of a token resource lookup method that * uses a JSON file as a source of user data. It takes a single configuration * argument: * <ul> * <li>users-file -- The path to a file containing user data. This file * should consist of a JSON object in which each field is a username, and * each field value is an object containing user attributes.</li> * </ul> */ public class ExampleTokenResourceLookupMethod extends TokenResourceLookupMethod { private static final String EXTENSION_NAME = "Example Token Resource Lookup Method"; private static final String EXTENSION_DESCRIPTION = "This token resource lookup method demonstrates the process for " + "creating a third-party token resource lookup method. Given a " + "token validation result object produced by an access token " + "validator, it uses the token subject value to look up a token " + "owner resource from a file containing user data."; private static final String ARG_USERS_FILE = "users-file"; private static final String ARG_USERS_FILE_DESCRIPTION = "The path to a file containing user data. The file should consist of a " + "JSON object where each field is a username, and each value is an " + "object containing the user's attributes."; private static final ObjectMapper MAPPER = new ObjectMapper(); private BrokerContext serverContext; private Map<String, ObjectNode> data; /** * {@inheritDoc} */ @Override public String getExtensionName() { return EXTENSION_NAME; } /** * {@inheritDoc} */ @Override public String[] getExtensionDescription() { return new String[] { EXTENSION_DESCRIPTION }; } /** * {@inheritDoc} */ @Override public void initializeTokenResourceLookupMethod( final BrokerContext serverContext, final TokenResourceLookupMethodConfig config, final ArgumentParser parser) throws Exception { super.initializeTokenResourceLookupMethod(serverContext, config, parser); this.serverContext = serverContext; final FileArgument usersFileArgument = parser.getFileArgument(ARG_USERS_FILE); try { this.data = Collections.unmodifiableMap( loadData(usersFileArgument.getValue())); } catch (IOException e) { throw new LDAPException(ResultCode.OTHER, "Failed to load user data file: " + e.getMessage(), e); } } /** * {@inheritDoc} */ @Override public void defineConfigArguments(final ArgumentParser parser) throws ArgumentException { Argument usersFileArgument = new FileArgument( null, ARG_USERS_FILE, true, 1, null, ARG_USERS_FILE_DESCRIPTION, true, true, true, false); parser.addArgument(usersFileArgument); } /** * {@inheritDoc} */ public boolean isConfigurationAcceptable( final TokenResourceLookupMethodConfig config, final ArgumentParser parser, final List<String> unacceptableReasons) { try { final FileArgument usersFileArgument = parser.getFileArgument(ARG_USERS_FILE); loadData(usersFileArgument.getValue()); } catch (IOException e) { unacceptableReasons.add("Failed to load user data file: " + e.getMessage()); return false; } return true; } /** * {@inheritDoc} */ public synchronized ResultCode applyConfiguration( final TokenResourceLookupMethodConfig config, final ArgumentParser parser, final List<String> adminActionsRequired, final List<String> messages) { try { final FileArgument usersFileArgument = parser.getFileArgument(ARG_USERS_FILE); this.data = Collections.unmodifiableMap( loadData(usersFileArgument.getValue())); } catch (IOException e) { messages.add("Failed to load user data file: " + e.getMessage()); return ResultCode.OTHER; } return ResultCode.SUCCESS; } /** * {@inheritDoc} */ @Override public TokenOwnerPrincipal lookupTokenOwner( final TokenValidationResult tokenValidationResult) { try { String userId = tokenValidationResult.getTokenSubject(); if (userId == null) { serverContext.logTraceMessage( LogSeverity.MILD_WARNING, "Access token has no subject; skipping owner lookup"); return null; } serverContext.logTraceMessage( LogSeverity.INFO, String.format("Looking up user id '%s'", userId)); ObjectNode userData = searchData(userId); if (userData != null) { serverContext.logTraceMessage( LogSeverity.INFO, String.format("User id '%s' found", userId)); return new TokenOwnerPrincipal() { @Override public String getName() { return userId; } @Override public ObjectNode getResource() { return userData; } }; } serverContext.logTraceMessage( LogSeverity.MILD_WARNING, String.format("User id '%s' not found", userId)); return null; } catch (Exception e) { serverContext.logTraceMessage( LogSeverity.SEVERE_ERROR, "Token owner lookup failed: " + e.getMessage()); return null; } } /** * Loads data from a JSON file containing user data. The contents of this file * should be a JSON object where each field is a username and each field * value is an object containing user attributes. * * @param usersFile * A user data file. * @return The parsed user data. * @throws IOException * if an error occurs while reading or parsing the user data file. */ private Map<String, ObjectNode> loadData(final File usersFile) throws IOException { return MAPPER.readValue(usersFile, new TypeReference<Map<String, ObjectNode>>() {}); } /** * Searches the in-memory user data for the given user. * * @param username * The username to search for. * @return The matching user data object, or {@code null} if the user was * not found. */ private ObjectNode searchData(final String username) { return data.get(username); } }