com.networknt.rule.generic.token.TokenAction Maven / Gradle / Ivy
package com.networknt.rule.generic.token;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.networknt.config.Config;
import com.networknt.config.JsonMapper;
import com.networknt.proxy.PathPrefixAuth;
import com.networknt.rule.RuleActionValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
 * Action class that describes the token request, where the token can be found in the response,
 * and where to save the data to.
 *
 * @author Kalev Gonvick
 */
public final class TokenAction {
    private static final Logger LOG = LoggerFactory.getLogger(TokenAction.class);
    private enum TokenDirection {
        REQUEST,
        RESPONSE,
        NONE
    }
    private static final String CONFIG_PREFIX = "config";
    private static final String RESPONSE_PREFIX = "response";
    static final String REQUEST_HEADERS = "requestHeaders";
    static final String REQUEST_BODY_ENTRIES = "requestBodyEntries";
    static final String SOURCE_HEADERS = "sourceHeaders";
    static final String SOURCE_BODY_ENTRIES = "sourceBodyEntries";
    static final String TOKEN_DIRECTION = "tokenDirection";
    static final String DESTINATION_HEADERS = "destinationHeaders";
    static final String DESTINATION_BODY_ENTRIES = "destinationBodyEntries";
    private final Collection actionValues;
    private HttpRequest request;
    private final PathPrefixAuth pathPrefixAuth;
    String[] requestHeaders = null;
    String[] requestBodyEntries = null;
    String requestContentType = null;
    String[] sourceHeaders = null;
    String[] sourceBodyEntries = null;
    String[] destinationHeaders = null;
    String[] destinationBodyEntries = null;
    TokenDirection tokenDirection = TokenDirection.NONE;
    private final TokenActionVariables tokenActionVariables = new TokenActionVariables();
    public TokenAction(final Collection actionValues, final PathPrefixAuth pathPrefixAuth) {
        this.actionValues = actionValues;
        this.pathPrefixAuth = pathPrefixAuth;
        this.tokenActionVariables.parseVariablesFromObject(CONFIG_PREFIX, this.pathPrefixAuth);
        this.parseActionValues();
    }
    /**
     * Parse the incoming rule actionValues.
     */
    private void parseActionValues() {
        for (var action : this.actionValues) {
            switch (action.getActionValueId()) {
                /* We can resolve request headers because the config is already loaded. */
                case REQUEST_HEADERS: {
                    String[] temp = action.getValue().split(",");
                    this.requestHeaders = this.tokenActionVariables.resolveArrayValues(temp);
                    break;
                }
                /* We can resolve request body entries because the config is already loaded. */
                case REQUEST_BODY_ENTRIES: {
                    String[] temp = action.getValue().split(",");
                    this.requestBodyEntries = this.tokenActionVariables.resolveArrayValues(temp);
                    break;
                }
                case TOKEN_DIRECTION: {
                    try {
                        this.tokenDirection = TokenDirection.valueOf(action.getValue());
                    } catch (IllegalArgumentException e) {
                        this.tokenDirection = TokenDirection.NONE;
                    }
                    break;
                }
                case DESTINATION_HEADERS: {
                    this.destinationHeaders = action.getValue().split(",");
                    break;
                }
                case DESTINATION_BODY_ENTRIES: {
                    this.destinationBodyEntries = action.getValue().split(",");
                    break;
                }
                case SOURCE_HEADERS: {
                    this.sourceHeaders = action.getValue().split(",");
                    break;
                }
                case SOURCE_BODY_ENTRIES: {
                    this.sourceBodyEntries = action.getValue().split(",");
                    break;
                }
                default: {
                    LOG.error("Unknown actionValueId '{}'.", action.getActionValueId());
                    break;
                }
            }
        }
    }
    /**
     * Builds the Http request based on the provided configuration.
     */
    public void buildRequest() {
        final var builder = HttpRequest.newBuilder();
        try {
            builder.uri(new URI(this.pathPrefixAuth.getTokenUrl()));
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
        /* handle request headers */
        if (this.isValidArrayLength(this.requestHeaders)) {
            for (int x = 0; x < this.requestHeaders.length; x = x + 2) {
                if (this.requestHeaders[x].equalsIgnoreCase("Content-Type"))
                    this.requestContentType = this.requestHeaders[x + 1];
                builder.header(this.requestHeaders[x], this.requestHeaders[x + 1]);
            }
        }
        /* handle request body */
        if (this.isValidArrayLength(this.requestBodyEntries)) {
            /* add body key + value pairs to the request */
            final Map parameters = new HashMap<>();
            for (int x = 0; x < this.requestBodyEntries.length; x = x + 2)
                parameters.put(this.requestBodyEntries[x], this.requestBodyEntries[x + 1]);
            String body;
            /* format the body */
            if (this.isUrlEncoded()) {
                body = parameters.entrySet().stream().map(
                        e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8)
                ).collect(Collectors.joining("&"));
            } else {
                try {
                    body = Config.getInstance().getMapper().writeValueAsString(parameters);
                } catch (JsonProcessingException e) {
                    LOG.error("Could not convert body parameters to string format: {}", e.getMessage());
                    return;
                }
            }
            /* only POST requests are supported right now. */
            builder.POST(HttpRequest.BodyPublishers.ofString(body));
        }
        this.request = builder.build();
    }
    /**
     * Checks to make sure the array is not null and has an even number of elements.
     *
     * @param arr   - array to check
     * @return      - true if the array is valid.
     */
    private boolean isValidArrayLength(Object[] arr) {
        return arr != null && (arr.length & 1) == 0;
    }
    private boolean isUrlEncoded() {
        return this.requestContentType != null
                && this.requestContentType.equalsIgnoreCase("application/x-www-form-urlencoded");
    }
    /**
     * Send the token request and save the result to the resultMap.
     *
     * @param client - Java Http client
     * @param resultMap - rule engine resultMap
     */
    public void requestToken(final HttpClient client, final Map resultMap) {
        try {
            final HttpResponse> response = client.send(this.request, HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() == 200) {
                final var tokenResponseMap = new HashMap();
                if (this.sourceHeaders != null)
                    tokenResponseMap.putAll(parseTokenResponseArrayVariables(
                            this.sourceHeaders,
                            response.headers().toString()
                            ));
                if (this.sourceBodyEntries != null)
                    tokenResponseMap.putAll(parseTokenResponseArrayVariables(
                                    this.sourceBodyEntries,
                                    response.body().toString()
                            ));
                this.tokenActionVariables.parseVariablesFromMap(RESPONSE_PREFIX, tokenResponseMap);
                this.destinationHeaders = this.tokenActionVariables.resolveArrayValues(this.destinationHeaders);
                this.destinationBodyEntries = this.tokenActionVariables.resolveArrayValues(this.destinationBodyEntries);
                /* update path prefix auth token info */
                if (tokenResponseMap.containsKey("accessToken")) {
                    this.pathPrefixAuth.setAccessToken((String)tokenResponseMap.get("accessToken"));
                }
                if (tokenResponseMap.containsKey("grantType")) {
                    this.pathPrefixAuth.setGrantType((String)tokenResponseMap.get("grantType"));
                }
                if (tokenResponseMap.containsKey("expiration")) {
                    this.pathPrefixAuth.setExpiration((Integer)tokenResponseMap.get("expiration"));
                } else {
                    /* TODO - should expiration be set statically like this? */
                    this.pathPrefixAuth.setExpiration(System.currentTimeMillis() + this.pathPrefixAuth.getTokenTtl() * 1000L - 60000);
                }
                LOG.trace("Received a new token '{}' and cached it with an expiration time of '{}'.",
                        this.pathPrefixAuth.getAccessToken() != null ? this.pathPrefixAuth.getAccessToken().substring(0, 20) : null,
                        this.pathPrefixAuth.getExpiration()
                );
                /* update the contents of the resultMap */
                this.updateResultMap(resultMap);
            } else LOG.error("Error in getting the token with status code {} and body {}", response.statusCode(), response.body());
        } catch (Exception e) {
            LOG.error("Exception:", e);
        }
    }
    private void updateResultMap(final Map resultMap) {
        /* update result map */
        switch (this.tokenDirection) {
            case REQUEST: {
                if (this.destinationBodyEntries != null) {
                    final var requestBodyMap = createUpdateMap(this.destinationBodyEntries);
                    resultMap.put("requestBody", requestBodyMap);
                }
                if (this.destinationHeaders != null) {
                    final var requestHeadersMap = createUpdateMap(this.destinationHeaders);
                    final var updateMap = new HashMap();
                    updateMap.put("update", requestHeadersMap);
                    resultMap.put("requestHeaders", updateMap);
                }
                break;
            }
            case RESPONSE: {
                if (this.destinationBodyEntries != null) {
                    final var responseBodyMap = createUpdateMap(this.destinationBodyEntries);
                    resultMap.put("responseBody", responseBodyMap);
                }
                if (this.destinationHeaders != null) {
                    final var responseHeadersMap = createUpdateMap(this.destinationHeaders);
                    final var updateMap = new HashMap();
                    updateMap.put("update", responseHeadersMap);
                    resultMap.put("responseHeaders", updateMap);
                }
                break;
            }
            case NONE:
            default:
                break;
        }
    }
    public void useCachedToken(final Map resultMap) {
        final var tokenResponseMap = new HashMap<>(Config.getInstance().getMapper().convertValue(
                this.pathPrefixAuth,
                new TypeReference             © 2015 - 2025 Weber Informatics LLC | Privacy Policy