com.netflix.genie.web.security.AbstractAPISecurityIntegrationTests.java Source code

Java tutorial

Introduction

Here is the source code for com.netflix.genie.web.security.AbstractAPISecurityIntegrationTests.java

Source

/*
 *
 *  Copyright 2016 Netflix, Inc.
 *
 *     Licensed 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 com.netflix.genie.web.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.util.ISO8601DateFormat;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.netflix.genie.common.dto.Application;
import com.netflix.genie.common.dto.ApplicationStatus;
import com.netflix.genie.common.dto.Cluster;
import com.netflix.genie.common.dto.ClusterStatus;
import com.netflix.genie.common.dto.Command;
import com.netflix.genie.common.dto.CommandStatus;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import java.util.TimeZone;
import java.util.UUID;

/**
 * Shared tests for accessing API resources. Any API configuration integration tests should extend this for consistent
 * behavior.
 *
 * @author tgianos
 * @since 3.0.0
 */
public abstract class AbstractAPISecurityIntegrationTests {

    private static final Application APPLICATION = new Application.Builder(UUID.randomUUID().toString(),
            UUID.randomUUID().toString(), UUID.randomUUID().toString(), ApplicationStatus.ACTIVE).build();

    private static final Cluster CLUSTER = new Cluster.Builder(UUID.randomUUID().toString(),
            UUID.randomUUID().toString(), UUID.randomUUID().toString(), ClusterStatus.UP).build();

    private static final Command COMMAND = new Command.Builder(UUID.randomUUID().toString(),
            UUID.randomUUID().toString(), UUID.randomUUID().toString(), CommandStatus.ACTIVE,
            UUID.randomUUID().toString(), 1000L).build();

    private static final ObjectMapper OBJECT_MAPPER;

    static {
        OBJECT_MAPPER = new ObjectMapper().setTimeZone(TimeZone.getTimeZone("UTC"))
                .setDateFormat(new ISO8601DateFormat()).registerModule(new Jdk8Module());
    }

    private static final String APPLICATIONS_API = "/api/v3/applications";
    private static final String CLUSTERS_API = "/api/v3/clusters";
    private static final String COMMANDS_API = "/api/v3/commands";
    private static final String JOBS_API = "/api/v3/jobs";

    private static final ResultMatcher OK = MockMvcResultMatchers.status().isOk();
    private static final ResultMatcher BAD_REQUEST = MockMvcResultMatchers.status().isBadRequest();
    private static final ResultMatcher CREATED = MockMvcResultMatchers.status().isCreated();
    private static final ResultMatcher NO_CONTENT = MockMvcResultMatchers.status().isNoContent();
    private static final ResultMatcher NOT_FOUND = MockMvcResultMatchers.status().isNotFound();
    private static final ResultMatcher FORBIDDEN = MockMvcResultMatchers.status().isForbidden();
    private static final ResultMatcher UNAUTHORIZED = MockMvcResultMatchers.status().isUnauthorized();

    @Value("${management.context-path}")
    private String actuatorEndpoint;

    @Autowired
    private WebApplicationContext context;

    private MockMvc mvc;

    /**
     * What ResultMatcher this class should return for unauthorized calls. For example x509 returns 403 while OAuth2
     * returns 401 when a call is made while unauthenticated.
     *
     * @return The result matcher to use when a user isn't logged in
     */
    public abstract ResultMatcher getUnauthorizedExpectedStatus();

    /**
     * Setup for the tests.
     */
    @Before
    public void setup() {
        this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
                .apply(SecurityMockMvcConfigurers.springSecurity()).build();
    }

    /**
     * Make sure we can get root.
     *
     * @throws Exception on any error
     */
    @Test
    public void canGetRoot() throws Exception {
        this.mvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.status().isOk());
    }

    /**
     * Make sure we can't call any API if not authenticated.
     *
     * @throws Exception on any error
     */
    @Test
    public void cantCallAnyAPIIfUnauthenticated() throws Exception {
        final ResultMatcher expectedUnauthenticatedStatus = this.getUnauthorizedExpectedStatus();
        this.get(APPLICATIONS_API, expectedUnauthenticatedStatus);
        this.get(CLUSTERS_API, expectedUnauthenticatedStatus);
        this.get(COMMANDS_API, expectedUnauthenticatedStatus);
        this.get(JOBS_API, expectedUnauthenticatedStatus);
        this.checkActuatorEndpoints(UNAUTHORIZED);
    }

    /**
     * Make sure we can't call anything under admin control as a regular user.
     *
     * @throws Exception on any error
     */
    @Test
    @WithMockUser
    public void cantCallAdminAPIsAsRegularUser() throws Exception {
        this.get(APPLICATIONS_API, OK);
        this.delete(APPLICATIONS_API, FORBIDDEN);
        this.post(APPLICATIONS_API, APPLICATION, FORBIDDEN);
        this.get(APPLICATIONS_API + "/" + UUID.randomUUID().toString(), NOT_FOUND);
        this.put(APPLICATIONS_API + "/" + UUID.randomUUID().toString(), APPLICATION, FORBIDDEN);

        this.get(CLUSTERS_API, OK);
        this.delete(CLUSTERS_API, FORBIDDEN);
        this.post(CLUSTERS_API, CLUSTER, FORBIDDEN);
        this.get(CLUSTERS_API + "/" + UUID.randomUUID().toString(), NOT_FOUND);
        this.put(CLUSTERS_API + "/" + UUID.randomUUID().toString(), CLUSTER, FORBIDDEN);

        this.get(COMMANDS_API, OK);
        this.delete(COMMANDS_API, FORBIDDEN);
        this.post(COMMANDS_API, COMMAND, FORBIDDEN);
        this.get(COMMANDS_API + "/" + UUID.randomUUID().toString(), NOT_FOUND);
        this.put(COMMANDS_API + "/" + UUID.randomUUID().toString(), COMMAND, FORBIDDEN);

        this.get(JOBS_API, OK);
        this.post(JOBS_API, "{\"key\":\"value\"}", BAD_REQUEST);
        this.get(JOBS_API + "/" + UUID.randomUUID().toString(), NOT_FOUND);
        this.delete(JOBS_API + "/" + UUID.randomUUID().toString(), NOT_FOUND);

        this.checkActuatorEndpoints(FORBIDDEN);
    }

    /**
     * Make sure we get get anything under admin control if we're an admin.
     *
     * @throws Exception on any error
     */
    @Test
    @WithMockUser(roles = { "USER", "ADMIN" })
    public void canCallAdminAPIsAsAdminUser() throws Exception {
        this.get(APPLICATIONS_API, OK);
        this.delete(APPLICATIONS_API, NO_CONTENT);
        this.post(APPLICATIONS_API, APPLICATION, CREATED);
        this.get(APPLICATIONS_API + "/" + UUID.randomUUID().toString(), NOT_FOUND);
        this.put(APPLICATIONS_API + "/" + UUID.randomUUID().toString(), APPLICATION, NOT_FOUND);

        this.get(CLUSTERS_API, OK);
        this.delete(CLUSTERS_API, NO_CONTENT);
        this.post(CLUSTERS_API, CLUSTER, CREATED);
        this.get(CLUSTERS_API + "/" + UUID.randomUUID().toString(), NOT_FOUND);
        this.put(CLUSTERS_API + "/" + UUID.randomUUID().toString(), CLUSTER, NOT_FOUND);

        this.get(COMMANDS_API, OK);
        this.delete(COMMANDS_API, NO_CONTENT);
        this.post(COMMANDS_API, COMMAND, CREATED);
        this.get(COMMANDS_API + "/" + UUID.randomUUID().toString(), NOT_FOUND);
        this.put(COMMANDS_API + "/" + UUID.randomUUID().toString(), COMMAND, NOT_FOUND);

        this.get(JOBS_API, OK);
        this.post(JOBS_API, "{\"key\":\"value\"}", BAD_REQUEST);
        this.get(JOBS_API + "/" + UUID.randomUUID().toString(), NOT_FOUND);
        this.delete(JOBS_API + "/" + UUID.randomUUID().toString(), NOT_FOUND);

        this.checkActuatorEndpoints(OK);
    }

    private void post(final String endpoint, final Object body, final ResultMatcher expectedStatus)
            throws Exception {
        this.mvc.perform(MockMvcRequestBuilders.post(endpoint).contentType(MediaType.APPLICATION_JSON)
                .content(OBJECT_MAPPER.writeValueAsBytes(body))).andExpect(expectedStatus);
    }

    private void put(final String endpoint, final Object body, final ResultMatcher expectedStatus)
            throws Exception {
        this.mvc.perform(MockMvcRequestBuilders.put(endpoint).contentType(MediaType.APPLICATION_JSON)
                .content(OBJECT_MAPPER.writeValueAsBytes(body))).andExpect(expectedStatus);
    }

    private void get(final String endpoint, final ResultMatcher expectedStatus) throws Exception {
        this.mvc.perform(MockMvcRequestBuilders.get(endpoint)).andExpect(expectedStatus);
    }

    private void delete(final String endpoint, final ResultMatcher expectedStatus) throws Exception {
        this.mvc.perform(MockMvcRequestBuilders.delete(endpoint)).andExpect(expectedStatus);
    }

    private void checkActuatorEndpoints(final ResultMatcher expectedResult) throws Exception {
        // See: https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html
        this.get(this.actuatorEndpoint + "/autoconfig", expectedResult);
        this.get(this.actuatorEndpoint + "/auditevents", expectedResult);
        this.get(this.actuatorEndpoint + "/beans", expectedResult);
        this.get(this.actuatorEndpoint + "/configprops", expectedResult);
        this.get(this.actuatorEndpoint + "/dump", expectedResult);
        this.get(this.actuatorEndpoint + "/env", expectedResult);
        this.get(this.actuatorEndpoint + "/health", OK);
        this.get(this.actuatorEndpoint + "/info", OK);
        this.get(this.actuatorEndpoint + "/loggers", expectedResult);
        this.get(this.actuatorEndpoint + "/mappings", expectedResult);
        this.get(this.actuatorEndpoint + "/metrics", expectedResult);
        this.get(this.actuatorEndpoint + "/trace", expectedResult);
    }
}