KillBillApiDefinition.java

173 lines | 7.197 kB Blame History Raw Download
/*
 * Copyright 2014-2018 Groupon, Inc
 * Copyright 2014-2018 The Billing Project, LLC
 *
 * The Billing Project licenses this file to you under the Apache License, version 2.0
 * (the "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at:
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package org.killbill.billing.jaxrs.resources;

import java.util.List;

import javax.annotation.Nullable;

import org.killbill.billing.util.api.AuditLevel;
import org.killbill.billing.util.audit.AuditLog;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.jaxrs.config.ReaderListener;
import io.swagger.models.Model;
import io.swagger.models.Operation;
import io.swagger.models.Path;
import io.swagger.models.Swagger;
import io.swagger.models.auth.BasicAuthDefinition;
import io.swagger.models.parameters.BodyParameter;
import io.swagger.models.parameters.HeaderParameter;
import io.swagger.models.parameters.Parameter;
import io.swagger.models.parameters.PathParameter;
import io.swagger.models.parameters.QueryParameter;
import io.swagger.models.properties.Property;

import static org.killbill.billing.jaxrs.resources.JaxrsResource.HDR_CREATED_BY;
import static org.killbill.billing.jaxrs.resources.JaxrsResource.QUERY_AUDIT;

@SwaggerDefinition
public class KillBillApiDefinition implements ReaderListener {

    public static final String BASIC_AUTH_SCHEME = "basicAuth";

    @Override
    public void beforeScan(final io.swagger.jaxrs.Reader reader, final Swagger swagger) {
        BasicAuthDefinition basicAuthDefinition = new BasicAuthDefinition();
        swagger.addSecurityDefinition(BASIC_AUTH_SCHEME, basicAuthDefinition);
    }

    @Override
    public void afterScan(final io.swagger.jaxrs.Reader reader, final Swagger swagger) {

        final HeaderParameter apiKeyParam = new HeaderParameter();
        apiKeyParam.setName("X-Killbill-ApiKey");
        apiKeyParam.setType("string");
        apiKeyParam.setRequired(true);

        final HeaderParameter apiSecretParam = new HeaderParameter();
        apiSecretParam.setName("X-Killbill-ApiSecret");
        apiSecretParam.setType("string");
        apiSecretParam.setRequired(true);

        for (final String pathName : swagger.getPaths().keySet()) {
            final Path path = swagger.getPaths().get(pathName);
            decorateOperation(path.getGet(), pathName, "GET", apiKeyParam, apiSecretParam);
            decorateOperation(path.getPost(), pathName, "POST", apiKeyParam, apiSecretParam);
            decorateOperation(path.getPut(), pathName, "PUT", apiKeyParam, apiSecretParam);
            decorateOperation(path.getDelete(), pathName, "DELETE", apiKeyParam, apiSecretParam);
            decorateOperation(path.getOptions(), pathName, "OPTIONS", apiKeyParam, apiSecretParam);

        }

        for (final Model m : swagger.getDefinitions().values()) {
            if (m.getProperties() != null) {
                for (final Property p : m.getProperties().values()) {
                    p.setReadOnly(false);
                }
            }
        }
    }

    private void decorateOperation(final Operation op, final String pathName, final String httpMethod, final HeaderParameter apiKeyParam, final HeaderParameter apiSecretParam) {
        if (op != null) {
            op.addSecurity(BASIC_AUTH_SCHEME, null);
            if (requiresTenantInformation(pathName, httpMethod)) {
                op.addParameter(apiKeyParam);
                op.addParameter(apiSecretParam);
            }

            for (Parameter p : op.getParameters()) {
                if (p instanceof BodyParameter) {
                    p.setRequired(true);
                } else if (p instanceof PathParameter) {
                    p.setRequired(true);
                } else if (p instanceof HeaderParameter) {
                    if (p.getName().equals(HDR_CREATED_BY)) {
                        p.setRequired(true);
                    }
                } else if (p instanceof QueryParameter) {
                    QueryParameter qp = (QueryParameter) p;
                    if (qp.getName().equals(QUERY_AUDIT)) {
                        qp.setName("auditLevel");
                        qp.setRequired(false);
                        qp.setType("string");
                        final List<String> values = ImmutableList.copyOf(Iterables.transform(ImmutableList.<AuditLevel>copyOf(AuditLevel.values()), new Function<AuditLevel, String>() {
                            @Override
                            public String apply(final AuditLevel input) {
                                return input.toString();
                            }
                        }));
                        qp.setEnum(values);
                    }
                }
            }
        }
    }

    public static boolean requiresTenantInformation(final String path, final String httpMethod) {
        boolean shouldSkipTenantInfoForRequests = (
                // Chicken - egg problem
                isTenantCreationRequest(path, httpMethod) ||
                // Retrieve user permissions should not require tenant info since this is cross tenants
                isPermissionRequest(path) ||
                // Node request are cross tenant
                isNodeInfoRequest(path) ||
                // See KillBillShiroWebModule#CorsBasicHttpAuthenticationFilter
                isOptionsRequest(httpMethod) ||
                // Shift the responsibility to the plugin
                isPluginRequest(path) ||
                // Static resources (welcome screen, Swagger, etc.)
                isNotKbNorPluginResourceRequest(path, httpMethod));
        return !shouldSkipTenantInfoForRequests;
    }

    private static boolean isPermissionRequest(final String path) {
        return path != null && path.startsWith(JaxrsResource.SECURITY_PATH);
    }

    private static boolean isTenantCreationRequest(final String path, final String httpMethod) {
        return JaxrsResource.TENANTS_PATH.equals(path) && "POST".equalsIgnoreCase(httpMethod);
    }

    private static boolean isNodeInfoRequest(final String path) {
        return JaxrsResource.NODES_INFO_PATH.equals(path);
    }

    private static boolean isOptionsRequest(final String httpMethod) {
        return "OPTIONS".equalsIgnoreCase(httpMethod);
    }

    private static boolean isNotKbNorPluginResourceRequest(final String path, final String httpMethod) {
        return !isPluginRequest(path) && !isKbApiRequest(path) && "GET".equalsIgnoreCase(httpMethod);
    }

    private static boolean isKbApiRequest(final String path) {
        return path != null && path.startsWith(JaxrsResource.PREFIX);
    }

    private static boolean isPluginRequest(final String path) {
        return path != null && path.startsWith(JaxrsResource.PLUGINS_PATH);
    }

}