Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF 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.apache.nifi.registry.web.api; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.registry.NiFiRegistryTestApiApplication; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.kerberos.authentication.KerberosTicketValidation; import org.springframework.security.kerberos.authentication.KerberosTicketValidator; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.junit4.SpringRunner; import javax.ws.rs.core.Response; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Base64; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** * Deploy the Web API Application using an embedded Jetty Server for local integration testing, with the follow characteristics: * * - A NiFiRegistryProperties has to be explicitly provided to the ApplicationContext using a profile unique to this test suite. * - A NiFiRegistryClientConfig has been configured to create a client capable of completing one-way TLS * - The database is embed H2 using volatile (in-memory) persistence * - Custom SQL is clearing the DB before each test method by default, unless method overrides this behavior */ @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "spring.profiles.include=ITSecureKerberos") @Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = "classpath:db/clearDB.sql") public class SecureKerberosIT extends IntegrationTestBase { private static final String validKerberosTicket = "authenticate_me"; private static final String invalidKerberosTicket = "do_not_authenticate_me"; public static class MockKerberosTicketValidator implements KerberosTicketValidator { @Override public KerberosTicketValidation validateTicket(byte[] token) throws BadCredentialsException { boolean validTicket; try { validTicket = Arrays.equals(validKerberosTicket.getBytes("UTF-8"), token); } catch (UnsupportedEncodingException e) { throw new IllegalStateException(e); } if (!validTicket) { throw new BadCredentialsException( MockKerberosTicketValidator.class.getSimpleName() + " does not validate token"); } return new KerberosTicketValidation("kerberosUser@LOCALHOST", "HTTP/localhsot@LOCALHOST", null, null); } } @Configuration @Profile("ITSecureKerberos") @Import({ NiFiRegistryTestApiApplication.class, SecureITClientConfiguration.class }) public static class KerberosSpnegoTestConfiguration { @Primary @Bean public static KerberosTicketValidator kerberosTicketValidator() { return new MockKerberosTicketValidator(); } } private String adminAuthToken; @Before public void generateAuthToken() { String validTicket = new String( Base64.getEncoder().encode(validKerberosTicket.getBytes(Charset.forName("UTF-8")))); final String token = client.target(createURL("/access/token/kerberos")).request() .header("Authorization", "Negotiate " + validTicket).post(null, String.class); adminAuthToken = token; } @Test public void testTokenGenerationAndAccessStatus() throws Exception { // Note: this test intentionally does not use the token generated // for nifiadmin by the @Before method // Given: the client and server have been configured correctly for Kerberos SPNEGO authentication String expectedJwtPayloadJson = "{" + "\"sub\":\"kerberosUser@LOCALHOST\"," + "\"preferred_username\":\"kerberosUser@LOCALHOST\"," + "\"iss\":\"KerberosSpnegoIdentityProvider\"" + "}"; String expectedAccessStatusJson = "{" + "\"identity\":\"kerberosUser@LOCALHOST\"," + "\"anonymous\":false}"; // When: the /access/token/kerberos endpoint is accessed with no credentials final Response tokenResponse1 = client.target(createURL("/access/token/kerberos")).request().post(null, Response.class); // Then: the server returns 401 Unauthorized with an authenticate challenge header assertEquals(401, tokenResponse1.getStatus()); assertNotNull(tokenResponse1.getHeaders().get("www-authenticate")); assertEquals(1, tokenResponse1.getHeaders().get("www-authenticate").size()); assertEquals("Negotiate", tokenResponse1.getHeaders().get("www-authenticate").get(0)); // When: the /access/token/kerberos endpoint is accessed again with an invalid ticket String invalidTicket = new String( java.util.Base64.getEncoder().encode(invalidKerberosTicket.getBytes(Charset.forName("UTF-8")))); final Response tokenResponse2 = client.target(createURL("/access/token/kerberos")).request() .header("Authorization", "Negotiate " + invalidTicket).post(null, Response.class); // Then: the server returns 401 Unauthorized assertEquals(401, tokenResponse2.getStatus()); // When: the /access/token/kerberos endpoint is accessed with a valid ticket String validTicket = new String( Base64.getEncoder().encode(validKerberosTicket.getBytes(Charset.forName("UTF-8")))); final Response tokenResponse3 = client.target(createURL("/access/token/kerberos")).request() .header("Authorization", "Negotiate " + validTicket).post(null, Response.class); // Then: the server returns 200 OK with a JWT in the body assertEquals(201, tokenResponse3.getStatus()); String token = tokenResponse3.readEntity(String.class); assertTrue(StringUtils.isNotEmpty(token)); String[] jwtParts = token.split("\\."); assertEquals(3, jwtParts.length); String jwtPayload = new String(Base64.getDecoder().decode(jwtParts[1]), "UTF-8"); JSONAssert.assertEquals(expectedJwtPayloadJson, jwtPayload, false); // When: the token is returned in the Authorization header final Response accessResponse = client.target(createURL("access")).request() .header("Authorization", "Bearer " + token).get(Response.class); // Then: the server acknowledges the client has access assertEquals(200, accessResponse.getStatus()); String accessStatus = accessResponse.readEntity(String.class); JSONAssert.assertEquals(expectedAccessStatusJson, accessStatus, false); } @Test public void testGetCurrentUser() throws Exception { // Given: the client is connected to an unsecured NiFi Registry String expectedJson = "{" + "\"identity\":\"kerberosUser@LOCALHOST\"," + "\"anonymous\":false," + "\"resourcePermissions\":{" + "\"anyTopLevelResource\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," + "\"buckets\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," + "\"tenants\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," + "\"policies\":{\"canRead\":true,\"canWrite\":true,\"canDelete\":true}," + "\"proxy\":{\"canRead\":false,\"canWrite\":true,\"canDelete\":false}}" + "}"; // When: the /access endpoint is queried using a JWT for the kerberos user final Response response = client.target(createURL("/access")).request() .header("Authorization", "Bearer " + adminAuthToken).get(Response.class); // Then: the server returns a 200 OK with the expected current user assertEquals(200, response.getStatus()); String actualJson = response.readEntity(String.class); JSONAssert.assertEquals(expectedJson, actualJson, false); } }