/*
* 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
*
*
* Copyright 2019-2021 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);
}
}