Skip to content

Commit

Permalink
Support additional information in the metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
fhanik committed Feb 3, 2016
1 parent 7ddd8f8 commit aad006f
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 80 deletions.
@@ -1,26 +1,27 @@
/*******************************************************************************
* 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.client; package org.cloudfoundry.identity.uaa.client;


import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;


import java.net.URL; import java.net.URL;


/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved.
* <p>
* 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.
* <p>
* 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.
*******************************************************************************/
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class ClientMetadata { public class ClientMetadata {


private String clientId; private String clientId;
private String clientName;
private String identityZoneId; private String identityZoneId;
private boolean showOnHomePage; private boolean showOnHomePage;
private URL appLaunchUrl; private URL appLaunchUrl;
Expand Down Expand Up @@ -67,4 +68,12 @@ public String getAppIcon() {
public void setAppIcon(String appIcon) { public void setAppIcon(String appIcon) {
this.appIcon = appIcon; this.appIcon = appIcon;
} }

public String getClientName() {
return clientName;
}

public void setClientName(String clientName) {
this.clientName = clientName;
}
} }
Expand Up @@ -12,12 +12,17 @@
*******************************************************************************/ *******************************************************************************/
package org.cloudfoundry.identity.uaa.client; package org.cloudfoundry.identity.uaa.client;


import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationService;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.Base64Utils; import org.springframework.util.Base64Utils;


Expand All @@ -27,23 +32,35 @@
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.Map;

import static org.cloudfoundry.identity.uaa.oauth.client.ClientConstants.CLIENT_NAME;
import static org.springframework.util.StringUtils.hasText;


public class JdbcClientMetadataProvisioning implements ClientMetadataProvisioning { public class JdbcClientMetadataProvisioning implements ClientMetadataProvisioning {


private static final Log logger = LogFactory.getLog(JdbcClientMetadataProvisioning.class); private static final Log logger = LogFactory.getLog(JdbcClientMetadataProvisioning.class);


private static final String CLIENT_METADATA_FIELDS = "client_id, identity_zone_id, show_on_home_page, app_launch_url, app_icon";
private static final String CLIENT_METADATA_FIELDS = "client_id, identity_zone_id, show_on_home_page, app_launch_url, app_icon, additional_information";
private static final String CLIENT_METADATA_QUERY = "select " + CLIENT_METADATA_FIELDS + " from oauth_client_details where client_id=? and identity_zone_id=?"; private static final String CLIENT_METADATA_QUERY = "select " + CLIENT_METADATA_FIELDS + " from oauth_client_details where client_id=? and identity_zone_id=?";
private static final String CLIENT_METADATAS_QUERY = "select " + CLIENT_METADATA_FIELDS + " from oauth_client_details where identity_zone_id=? and ((app_launch_url is not null and char_length(app_launch_url)>0) or (app_icon is not null and octet_length(app_icon)>0))"; private static final String CLIENT_METADATAS_QUERY = "select " + CLIENT_METADATA_FIELDS + " from oauth_client_details where identity_zone_id=? and ((app_launch_url is not null and char_length(app_launch_url)>0) or (app_icon is not null and octet_length(app_icon)>0))";
private static final String CLIENT_METADATA_UPDATE_FIELDS = "show_on_home_page, app_launch_url, app_icon"; private static final String CLIENT_METADATA_UPDATE_FIELDS = "show_on_home_page, app_launch_url, app_icon";
private static final String CLIENT_METADATA_UPDATE = "update oauth_client_details set " + CLIENT_METADATA_UPDATE_FIELDS.replace(",", "=?,") + "=?" + " where client_id=? and identity_zone_id=?"; private static final String CLIENT_METADATA_UPDATE = "update oauth_client_details set " + CLIENT_METADATA_UPDATE_FIELDS.replace(",", "=?,") + "=?" + " where client_id=? and identity_zone_id=?";


private JdbcTemplate template; private JdbcTemplate template;
private ClientDetailsService clientDetailsService;
private ClientRegistrationService clientRegistrationService;
private final RowMapper<ClientMetadata> mapper = new ClientMetadataRowMapper(); private final RowMapper<ClientMetadata> mapper = new ClientMetadataRowMapper();


JdbcClientMetadataProvisioning(JdbcTemplate template) { JdbcClientMetadataProvisioning(ClientDetailsService clientDetailsService,
ClientRegistrationService clientRegistrationService,
JdbcTemplate template) {
Assert.notNull(template); Assert.notNull(template);
Assert.notNull(clientDetailsService);
this.template = template; this.template = template;
this.clientDetailsService = clientDetailsService;
this.clientRegistrationService = clientRegistrationService;
} }


public void setTemplate(JdbcTemplate template) { public void setTemplate(JdbcTemplate template) {
Expand All @@ -65,6 +82,8 @@ public ClientMetadata retrieve(String clientId) {
@Override @Override
public ClientMetadata update(ClientMetadata resource) { public ClientMetadata update(ClientMetadata resource) {
logger.debug("Updating metadata for client: " + resource.getClientId()); logger.debug("Updating metadata for client: " + resource.getClientId());

updateClientNameIfNotEmpty(resource);
int updated = template.update(CLIENT_METADATA_UPDATE, ps -> { int updated = template.update(CLIENT_METADATA_UPDATE, ps -> {
int pos = 1; int pos = 1;
ps.setBoolean(pos++, resource.isShowOnHomePage()); ps.setBoolean(pos++, resource.isShowOnHomePage());
Expand All @@ -89,6 +108,15 @@ public ClientMetadata update(ClientMetadata resource) {
return resultingClientMetadata; return resultingClientMetadata;
} }


protected void updateClientNameIfNotEmpty(ClientMetadata resource) {
//we don't remove it, only set values
if (hasText(resource.getClientName())) {
BaseClientDetails client = (BaseClientDetails) clientDetailsService.loadClientByClientId(resource.getClientId());
client.addAdditionalInformation(CLIENT_NAME, resource.getClientName());
clientRegistrationService.updateClientDetails(client);
}
}



private class ClientMetadataRowMapper implements RowMapper<ClientMetadata> { private class ClientMetadataRowMapper implements RowMapper<ClientMetadata> {


Expand All @@ -105,7 +133,14 @@ public ClientMetadata mapRow(ResultSet rs, int rowNum) throws SQLException {
// it is safe to ignore this as client_metadata rows are always created from a ClientMetadata instance whose launch url property is strongly typed to URL // it is safe to ignore this as client_metadata rows are always created from a ClientMetadata instance whose launch url property is strongly typed to URL
} }
byte[] iconBytes = rs.getBytes(pos++); byte[] iconBytes = rs.getBytes(pos++);
if(iconBytes != null) { clientMetadata.setAppIcon(new String(Base64Utils.encode(iconBytes))); } if(iconBytes != null) {
clientMetadata.setAppIcon(new String(Base64Utils.encode(iconBytes)));
}
String json = rs.getString(pos++);
if (hasText(json)) {
Map<String,Object> additionalInformation = JsonUtils.readValue(json, new TypeReference<Map<String,Object>>() {});
clientMetadata.setClientName((String)additionalInformation.get(CLIENT_NAME));
}
return clientMetadata; return clientMetadata;
} }
} }
Expand Down
Expand Up @@ -19,6 +19,7 @@
import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
Expand Down Expand Up @@ -55,10 +56,6 @@ public class MultitenantJdbcClientDetailsService extends JdbcClientDetailsServic


private static final Log logger = LogFactory.getLog(MultitenantJdbcClientDetailsService.class); private static final Log logger = LogFactory.getLog(MultitenantJdbcClientDetailsService.class);




private JsonMapper mapper = createJsonMapper();

private static final String CLIENT_FIELDS_FOR_UPDATE = "resource_ids, scope, " private static final String CLIENT_FIELDS_FOR_UPDATE = "resource_ids, scope, "
+ "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, " + "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
+ "refresh_token_validity, additional_information, autoapprove, lastmodified"; + "refresh_token_validity, additional_information, autoapprove, lastmodified";
Expand Down Expand Up @@ -177,9 +174,10 @@ private Object[] getFields(ClientDetails clientDetails) {
private Object[] getFieldsForUpdate(ClientDetails clientDetails) { private Object[] getFieldsForUpdate(ClientDetails clientDetails) {
String json = null; String json = null;
try { try {
json = mapper.write(clientDetails.getAdditionalInformation()); json = JsonUtils.writeValueAsString(clientDetails.getAdditionalInformation());
} catch (Exception e) { } catch (Exception e) {
logger.warn("Could not serialize additional information: " + clientDetails, e); logger.warn("Could not serialize additional information: " + clientDetails, e);
throw new InvalidDataAccessResourceUsageException("Could not serialize additional information:"+clientDetails.getClientId(), e);
} }
return new Object[] { return new Object[] {
clientDetails.getResourceIds() != null ? StringUtils.collectionToCommaDelimitedString(clientDetails clientDetails.getResourceIds() != null ? StringUtils.collectionToCommaDelimitedString(clientDetails
Expand Down Expand Up @@ -268,13 +266,11 @@ public Log getLogger() {


/** /**
* Row mapper for ClientDetails. * Row mapper for ClientDetails.
* *
* @author Dave Syer * @author Dave Syer
* *
*/ */
private static class ClientDetailsRowMapper implements RowMapper<ClientDetails> { private static class ClientDetailsRowMapper implements RowMapper<ClientDetails> {
private JsonMapper mapper = createJsonMapper();

public ClientDetails mapRow(ResultSet rs, int rowNum) throws SQLException { public ClientDetails mapRow(ResultSet rs, int rowNum) throws SQLException {
BaseClientDetails details = new BaseClientDetails(rs.getString(1), rs.getString(3), rs.getString(4), BaseClientDetails details = new BaseClientDetails(rs.getString(1), rs.getString(3), rs.getString(4),
rs.getString(5), rs.getString(7), rs.getString(6)); rs.getString(5), rs.getString(7), rs.getString(6));
Expand All @@ -291,7 +287,7 @@ public ClientDetails mapRow(ResultSet rs, int rowNum) throws SQLException {
if (json != null) { if (json != null) {
try { try {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Object> additionalInformation = mapper.read(json, Map.class); Map<String, Object> additionalInformation = JsonUtils.readValue(json, Map.class);
details.setAdditionalInformation(additionalInformation); details.setAdditionalInformation(additionalInformation);
} catch (Exception e) { } catch (Exception e) {
logger.warn("Could not decode JSON for additional information: " + details, e); logger.warn("Could not decode JSON for additional information: " + details, e);
Expand All @@ -311,43 +307,6 @@ public ClientDetails mapRow(ResultSet rs, int rowNum) throws SQLException {
} }
} }


interface JsonMapper {
String write(Object input) throws Exception;

<T> T read(String input, Class<T> type) throws Exception;
}

private static JsonMapper createJsonMapper() {
return new JacksonMapper();
}

private static class JacksonMapper implements JsonMapper {

@Override
public String write(Object input) throws Exception {
return JsonUtils.writeValueAsString(input);
}

@Override
public <T> T read(String input, Class<T> type) throws Exception {
return JsonUtils.readValue(input, type);
}
}

private static class NotSupportedJsonMapper implements JsonMapper {
@Override
public String write(Object input) throws Exception {
throw new UnsupportedOperationException(
"Neither Jackson 1 nor 2 is available so JSON conversion cannot be done");
}

@Override
public <T> T read(String input, Class<T> type) throws Exception {
throw new UnsupportedOperationException(
"Neither Jackson 1 nor 2 is available so JSON conversion cannot be done");
}
}

@Override @Override
public int getTotalCount() { public int getTotalCount() {
Integer count = jdbcTemplate.queryForObject("select count(*) from oauth_client_details", Integer.class); Integer count = jdbcTemplate.queryForObject("select count(*) from oauth_client_details", Integer.class);
Expand Down
@@ -1,5 +1,5 @@
/******************************************************************************* /*******************************************************************************
* Cloud Foundry * Cloud Foundry
* Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * 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"). * This product is licensed to you under the Apache License, Version 2.0 (the "License").
Expand All @@ -13,7 +13,6 @@


package org.cloudfoundry.identity.uaa.client; package org.cloudfoundry.identity.uaa.client;


import org.cloudfoundry.identity.uaa.client.ClientAdminBootstrap;
import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants;
import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.test.JdbcTestBase;
import org.cloudfoundry.identity.uaa.zone.MultitenantJdbcClientDetailsService; import org.cloudfoundry.identity.uaa.zone.MultitenantJdbcClientDetailsService;
Expand All @@ -27,7 +26,6 @@
import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientRegistrationService; import org.springframework.security.oauth2.provider.ClientRegistrationService;
import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;


Expand All @@ -54,15 +52,15 @@ public class ClientAdminBootstrapTests extends JdbcTestBase {


private ClientAdminBootstrap bootstrap; private ClientAdminBootstrap bootstrap;


private JdbcClientDetailsService clientRegistrationService; private MultitenantJdbcClientDetailsService clientRegistrationService;
private ClientMetadataProvisioning clientMetadataProvisioning; private ClientMetadataProvisioning clientMetadataProvisioning;


@Before @Before
public void setUpClientAdminTests() throws Exception { public void setUpClientAdminTests() throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
bootstrap = new ClientAdminBootstrap(encoder); bootstrap = new ClientAdminBootstrap(encoder);
clientRegistrationService = new MultitenantJdbcClientDetailsService(dataSource); clientRegistrationService = new MultitenantJdbcClientDetailsService(dataSource);
clientMetadataProvisioning = new JdbcClientMetadataProvisioning(jdbcTemplate); clientMetadataProvisioning = new JdbcClientMetadataProvisioning(clientRegistrationService,clientRegistrationService,jdbcTemplate);
bootstrap.setClientRegistrationService(clientRegistrationService); bootstrap.setClientRegistrationService(clientRegistrationService);
bootstrap.setClientMetadataProvisioning(clientMetadataProvisioning); bootstrap.setClientMetadataProvisioning(clientMetadataProvisioning);
clientRegistrationService.setPasswordEncoder(encoder); clientRegistrationService.setPasswordEncoder(encoder);
Expand Down
@@ -1,8 +1,22 @@
/*******************************************************************************
* 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.client; package org.cloudfoundry.identity.uaa.client;


import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.test.JdbcTestBase;
import org.cloudfoundry.identity.uaa.util.PredicateMatcher; import org.cloudfoundry.identity.uaa.util.PredicateMatcher;
import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.cloudfoundry.identity.uaa.zone.MultitenantJdbcClientDetailsService;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.EmptyResultDataAccessException;
Expand All @@ -13,29 +27,20 @@
import java.util.List; import java.util.List;


import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;


/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved.
* <p>
* 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.
* <p>
* 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.
*******************************************************************************/
public class JdbcClientMetadataProvisioningTest extends JdbcTestBase { public class JdbcClientMetadataProvisioningTest extends JdbcTestBase {


public static final String CLIENT_NAME = "Test name";
JdbcClientMetadataProvisioning db; JdbcClientMetadataProvisioning db;


private RandomValueStringGenerator generator = new RandomValueStringGenerator(8); private RandomValueStringGenerator generator = new RandomValueStringGenerator(8);


@Before @Before
public void createDatasource() throws Exception { public void createDatasource() throws Exception {
db = new JdbcClientMetadataProvisioning(jdbcTemplate); MultitenantJdbcClientDetailsService clientService = new MultitenantJdbcClientDetailsService(dataSource);
db = new JdbcClientMetadataProvisioning(clientService, clientService, jdbcTemplate);
} }


@Test(expected = EmptyResultDataAccessException.class) @Test(expected = EmptyResultDataAccessException.class)
Expand Down Expand Up @@ -99,6 +104,24 @@ public void updateClientMetadata() throws Exception {
assertThat(updatedClientMetadata.getAppIcon(), is(newClientMetadata.getAppIcon())); assertThat(updatedClientMetadata.getAppIcon(), is(newClientMetadata.getAppIcon()));
} }


@Test
public void test_set_and_get_ClientName() throws Exception {
String clientId = generator.generate();
jdbcTemplate.execute("insert into oauth_client_details(client_id, identity_zone_id) values ('" + clientId + "', '" + IdentityZoneHolder.get().getId() + "')");
ClientMetadata data = createTestClientMetadata(clientId,
false,
null,
null);
data.setClientName(CLIENT_NAME);
db.update(data);
data = db.retrieve(clientId);
assertEquals(CLIENT_NAME, data.getClientName());
}





private ClientMetadata createTestClientMetadata(String clientId, boolean showOnHomePage, URL appLaunchUrl, String appIcon) throws MalformedURLException { private ClientMetadata createTestClientMetadata(String clientId, boolean showOnHomePage, URL appLaunchUrl, String appIcon) throws MalformedURLException {
ClientMetadata clientMetadata = new ClientMetadata(); ClientMetadata clientMetadata = new ClientMetadata();
clientMetadata.setClientId(clientId); clientMetadata.setClientId(clientId);
Expand Down
3 changes: 3 additions & 0 deletions uaa/src/main/webapp/WEB-INF/spring/oauth-clients.xml
Expand Up @@ -27,7 +27,10 @@
</bean> </bean>


<bean id="jdbcClientMetadataProvisioning" class="org.cloudfoundry.identity.uaa.client.JdbcClientMetadataProvisioning"> <bean id="jdbcClientMetadataProvisioning" class="org.cloudfoundry.identity.uaa.client.JdbcClientMetadataProvisioning">
<constructor-arg name="clientDetailsService" ref="jdbcClientDetailsService"/>
<constructor-arg name="clientRegistrationService" ref="jdbcClientDetailsService"/>
<constructor-arg name="template" ref="jdbcTemplate" /> <constructor-arg name="template" ref="jdbcTemplate" />

</bean> </bean>


<bean id="clientAdminBootstrap" class="org.cloudfoundry.identity.uaa.client.ClientAdminBootstrap"> <bean id="clientAdminBootstrap" class="org.cloudfoundry.identity.uaa.client.ClientAdminBootstrap">
Expand Down

0 comments on commit aad006f

Please sign in to comment.