/******************************************************************************* * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product includes a number of subcomponents with * separate copyright notices and license terms. Your use of these * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ package org.cloudfoundry.identity.uaa.mock.token; import org.apache.commons.codec.binary.Base64; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.junit.Before; import org.junit.Test; import org.springframework.restdocs.snippet.Snippet; import java.util.Collections; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; import static org.springframework.restdocs.payload.JsonFieldType.STRING; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class TokenKeyEndpointDocs extends InjectedMockContextTest { private static final String signKey = "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEowIBAAKCAQEA0m59l2u9iDnMbrXHfqkOrn2dVQ3vfBJqcDuFUK03d+1PZGbV\n" + "lNCqnkpIJ8syFppW8ljnWweP7+LiWpRoz0I7fYb3d8TjhV86Y997Fl4DBrxgM6KT\n" + "JOuE/uxnoDhZQ14LgOU2ckXjOzOdTsnGMKQBLCl0vpcXBtFLMaSbpv1ozi8h7DJy\n" + "VZ6EnFQZUWGdgTMhDrmqevfx95U/16c5WBDOkqwIn7Glry9n9Suxygbf8g5AzpWc\n" + "usZgDLIIZ7JTUldBb8qU2a0Dl4mvLZOn4wPojfj9Cw2QICsc5+Pwf21fP+hzf+1W\n" + "SRHbnYv8uanRO0gZ8ekGaghM/2H6gqJbo2nIJwIDAQABAoIBAHPV9rSfzllq16op\n" + "zoNetIJBC5aCcU4vJQBbA2wBrgMKUyXFpdSheQphgY7GP/BJTYtifRiS9RzsHAYY\n" + "pAlTQEQ9Q4RekZAdd5r6rlsFrUzL7Xj/CVjNfQyHPhPocNqwrkxp4KrO5eL06qcw\n" + "UzT7UtnoiCdSLI7IL0hIgJZP8J1uPNdXH+kkDEHE9xzU1q0vsi8nBLlim+ioYfEa\n" + "Q/Q/ovMNviLKVs+ZUz+wayglDbCzsevuU+dh3Gmfc98DJw6n6iClpd4fDPqvhxUO\n" + "BDeQT1mFeHxexDse/kH9nygxT6E4wlU1sw0TQANcT6sHReyHT1TlwnWlCQzoR3l2\n" + "RmkzUsECgYEA8W/VIkfyYdUd5ri+yJ3iLdYF2tDvkiuzVmJeA5AK2KO1fNc7cSPK\n" + "/sShHruc0WWZKWiR8Tp3d1XwA2rHMFHwC78RsTds+NpROs3Ya5sWd5mvmpEBbL+z\n" + "cl3AU9NLHVvsZjogmgI9HIMTTl4ld7GDsFMt0qlCDztqG6W/iguQCx8CgYEA3x/j\n" + "UkP45/PaFWd5c1DkWvmfmi9UxrIM7KeyBtDExGIkffwBMWFMCWm9DODw14bpnqAA\n" + "jH5AhQCzVYaXIdp12b+1+eOOckYHwzjWOFpJ3nLgNK3wi067jVp0N0UfgV5nfYw/\n" + "+YoHfYRCGsM91fowh7wLcyPPwmSAbQAKwbOZKfkCgYEAnccDdZ+m2iA3pitdIiVr\n" + "RaDzuoeHx/IfBHjMD2/2ZpS1aZwOEGXfppZA5KCeXokSimj31rjqkWXrr4/8E6u4\n" + "PzTiDvm1kPq60r7qi4eSKx6YD15rm/G7ByYVJbKTB+CmoDekToDgBt3xo+kKeyna\n" + "cUQqUdyieunM8bxja4ca3ukCgYAfrDAhomJ30qa3eRvFYcs4msysH2HiXq30/g0I\n" + "aKQ12FSjyZ0FvHEFuQvMAzZM8erByKarStSvzJyoXFWhyZgHE+6qDUJQOF6ruKq4\n" + "DyEDQb1P3Q0TSVbYRunOWrKRM6xvJvSB4LUVfSvBDsv9TumKqwfZDVFVn9yXHHVq\n" + "b6sjSQKBgDkcyYkAjpOHoG3XKMw06OE4OKpP9N6qU8uZOuA8ZF9ZyR7vFf4bCsKv\n" + "QH+xY/4h8tgL+eASz5QWhj8DItm8wYGI5lKJr8f36jk0JLPUXODyDAeN6ekXY9LI\n" + "fudkijw0dnh28LJqbkFF5wLNtATzyCfzjp+czrPMn9uqLNKt/iVD\n" + "-----END RSA PRIVATE KEY-----"; @Before public void setUp() throws Exception { setUp(signKey); } public void setUp(String signKey) throws Exception { IdentityZoneProvisioning provisioning = getWebApplicationContext().getBean(IdentityZoneProvisioning.class); IdentityZone uaa = provisioning.retrieve("uaa"); TokenPolicy tokenPolicy = new TokenPolicy(); tokenPolicy.setKeys(Collections.singletonMap("testKey", signKey)); uaa.getConfig().setTokenPolicy(tokenPolicy); provisioning.update(uaa); } @Test public void getTokenAsymmetricAuthenticated() throws Exception { String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64(("app:appclientsecret").getBytes())); Snippet responseFields = responseFields( fieldWithPath("kid").type(STRING).description("Key ID of key to be used for verification of the token."), fieldWithPath("alg").type(STRING).description("Encryption algorithm"), fieldWithPath("value").type(STRING).description("Verifier key"), fieldWithPath("kty").type(STRING).description("Key type (RSA)"), fieldWithPath("use").type(STRING).description("Public key use parameter - identifies intended use of the public key. (defaults to \"sig\")"), fieldWithPath("n").type(STRING).description("RSA key modulus"), fieldWithPath("e").type(STRING).description("RSA key public exponent") ); getMockMvc().perform( get("/token_key") .accept(APPLICATION_JSON) .header("Authorization", basicDigestHeaderValue)) .andExpect(status().isOk()) .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()),requestHeaders( headerWithName("Authorization").description("No authorization is required for requesting public keys.").optional() ), responseFields)); } @Test public void getTokenSymmetricAuthenticated() throws Exception { setUp("key"); try { String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64(("app:appclientsecret").getBytes())); Snippet responseFields = responseFields( fieldWithPath("kid").type(STRING).description("Key ID of key to be used for verification of the token."), fieldWithPath("alg").type(STRING).description("Encryption algorithm"), fieldWithPath("value").type(STRING).description("Verifier key"), fieldWithPath("kty").type(STRING).description("Key type (MAC)"), fieldWithPath("use").type(STRING).description("Public key use parameter - identifies intended use of the public key. (defaults to \"sig\")") ); getMockMvc().perform( get("/token_key") .accept(APPLICATION_JSON) .header("Authorization", basicDigestHeaderValue)) .andExpect(status().isOk()) .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestHeaders( headerWithName("Authorization").description("Uses basic authorization with `base64(resource_server:shared_secret)` assuming the caller (a resource server) is actually also a registered client and has `uaa.resource` authority") ), responseFields)); } finally { setUp(signKey); } } @Test public void checkTokenKeysValues() throws Exception { String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64(("app:appclientsecret").getBytes())); Snippet responseFields = responseFields( fieldWithPath("keys.[].kid").type(STRING).description("Key ID of key to be used for verification of the token."), fieldWithPath("keys.[].alg").type(STRING).description("Encryption algorithm"), fieldWithPath("keys.[].value").type(STRING).description("Verifier key"), fieldWithPath("keys.[].kty").type(STRING).description("Key type (RSA or MAC)"), fieldWithPath("keys.[].use").type(STRING).description("Public key use parameter - identifies intended use of the public key. (defaults to \"sig\")"), fieldWithPath("keys.[].n").type(STRING).description("RSA key modulus").optional(), fieldWithPath("keys.[].e").type(STRING).description("RSA key public exponent").optional() ); getMockMvc().perform( get("/token_keys") .accept(APPLICATION_JSON) .header("Authorization", basicDigestHeaderValue)) .andExpect(status().isOk()) .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestHeaders( headerWithName("Authorization").description("Basic authorization with `base64(resource_server:shared_secret)` assuming the caller (a resource server) is actually also a registered client and has `uaa.resource` authority. Header not required (anonymous) for obtaining only asymmetric keys. `uaa.resource` authority also not required for obtaining only asymmetric keys should you choose to provide this header.") ), responseFields)); } }