Skip to content

Commit

Permalink
Delete clients from manifest upon startup.
Browse files Browse the repository at this point in the history
Because client delete is a 'chain of events' such as deleting approvals and tokens.
This operation happens after the context has started up, so that the event can propagate

[#142056541] https://www.pivotaltracker.com/story/show/142056541
  • Loading branch information
fhanik committed Mar 23, 2017
1 parent 00e759d commit 662f654
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 34 deletions.
Expand Up @@ -22,6 +22,8 @@

import java.lang.reflect.Method;

import static java.util.Optional.ofNullable;

public class EntityDeletedEvent<T> extends AbstractUaaEvent {

protected static final String dataFormat = "Class:%s; ID:%s";
Expand All @@ -45,7 +47,8 @@ public AuditEvent getAuditEvent() {
}

public String getObjectId() {
Method m = ReflectionUtils.findMethod(source.getClass(), "getId");
Method m = ofNullable(ReflectionUtils.findMethod(source.getClass(), "getId"))
.orElseGet(() -> ReflectionUtils.findMethod(source.getClass(), "getClientId"));
return m!=null ? (String)ReflectionUtils.invokeMethod(m, source) : String.valueOf(System.identityHashCode(source));
}
}
@@ -0,0 +1,68 @@
/*
* ****************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2017] 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.authentication;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;
import java.util.Collections;


public class SystemAuthentication implements Authentication {

public static final SystemAuthentication SYSTEM_AUTHENTICATION = new SystemAuthentication();

protected static final String PRINCIPAL = "uaa-system";

private SystemAuthentication() {
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptyList();
}

@Override
public Object getCredentials() {
return null;
}

@Override
public Object getDetails() {
return PRINCIPAL;
}

@Override
public Object getPrincipal() {
return PRINCIPAL;
}

@Override
public boolean isAuthenticated() {
return true;
}

@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {

}

@Override
public String getName() {
return PRINCIPAL;
}
}
Expand Up @@ -14,15 +14,23 @@

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent;
import org.cloudfoundry.identity.uaa.authentication.SystemAuthentication;
import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants;
import org.cloudfoundry.identity.uaa.user.UaaAuthority;
import org.cloudfoundry.identity.uaa.zone.ClientServicesExtension;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.ClientAlreadyExistsException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationService;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.util.StringUtils;

Expand All @@ -37,15 +45,19 @@
import java.util.Map;
import java.util.Set;

public class ClientAdminBootstrap implements InitializingBean {
import static java.util.Optional.ofNullable;

public class ClientAdminBootstrap implements InitializingBean, ApplicationListener<ContextRefreshedEvent>, ApplicationEventPublisherAware {

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

private Map<String, Map<String, Object>> clients = new HashMap<String, Map<String, Object>>();

private List<String> clientsToDelete = null;

private Collection<String> autoApproveClients = Collections.emptySet();

private ClientRegistrationService clientRegistrationService;
private ClientServicesExtension clientRegistrationService;

private ClientMetadataProvisioning clientMetadataProvisioning;

Expand All @@ -55,6 +67,8 @@ public class ClientAdminBootstrap implements InitializingBean {

private final PasswordEncoder passwordEncoder;

private ApplicationEventPublisher publisher;

public ClientAdminBootstrap(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
Expand Down Expand Up @@ -101,6 +115,10 @@ public void setClients(Map<String, Map<String, Object>> clients) {
}
}

public void setClientsToDelete(List<String> clientsToDelete) {
this.clientsToDelete = clientsToDelete;
}

/**
* A set of client ids that are unconditionally to be autoapproved
* (independent of the settings in the client
Expand All @@ -117,7 +135,7 @@ public void setAutoApproveClients(Collection<String> autoApproveClients) {
/**
* @param clientRegistrationService the clientRegistrationService to set
*/
public void setClientRegistrationService(ClientRegistrationService clientRegistrationService) {
public void setClientRegistrationService(ClientServicesExtension clientRegistrationService) {
this.clientRegistrationService = clientRegistrationService;
}

Expand Down Expand Up @@ -287,8 +305,8 @@ private ClientMetadata buildClientMetadata(Map<String, Object> map, String clien
}

protected boolean didPasswordChange(String clientId, String rawPassword) {
if (getPasswordEncoder()!=null && clientRegistrationService instanceof ClientDetailsService) {
ClientDetails existing = ((ClientDetailsService)clientRegistrationService).loadClientByClientId(clientId);
if (getPasswordEncoder()!=null) {
ClientDetails existing = clientRegistrationService.loadClientByClientId(clientId);
String existingPasswordHash = existing.getClientSecret();
return !getPasswordEncoder().matches(rawPassword, existingPasswordHash);
} else {
Expand All @@ -303,4 +321,30 @@ public ClientMetadataProvisioning getClientMetadataProvisioning() {
public void setClientMetadataProvisioning(ClientMetadataProvisioning clientMetadataProvisioning) {
this.clientMetadataProvisioning = clientMetadataProvisioning;
}

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
Authentication auth = SystemAuthentication.SYSTEM_AUTHENTICATION;
for (String clientId : ofNullable(clientsToDelete).orElse(Collections.emptyList())) {
try {
ClientDetails client = clientRegistrationService.loadClientByClientId(clientId);
logger.debug("Deleting client from manifest:"+clientId);
EntityDeletedEvent<ClientDetails> delete = new EntityDeletedEvent<>(client, auth);
publish(delete);
} catch (NoSuchClientException e) {
logger.debug("Ignoring delete for non existent client:"+clientId);
}
}
}

@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}

public void publish(ApplicationEvent event) {
if (publisher!=null) {
publisher.publishEvent(event);
}
}
}
@@ -0,0 +1,67 @@
/*
* ****************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2017] 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.authentication;

import org.junit.Test;

import static java.util.Collections.emptyList;
import static org.cloudfoundry.identity.uaa.authentication.SystemAuthentication.PRINCIPAL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

public class SystemAuthenticationTest {

private SystemAuthentication auth = SystemAuthentication.SYSTEM_AUTHENTICATION;

@Test
public void getAuthorities() throws Exception {
assertSame(emptyList(), auth.getAuthorities());
}

@Test
public void getCredentials() throws Exception {
assertNull(auth.getCredentials());
}

@Test
public void getDetails() throws Exception {
assertEquals(PRINCIPAL, auth.getDetails());
}

@Test
public void getPrincipal() throws Exception {
assertEquals(PRINCIPAL, auth.getPrincipal());
}

@Test
public void isAuthenticated() throws Exception {
assertTrue(auth.isAuthenticated());
}

@Test
public void setAuthenticated() throws Exception {
auth.setAuthenticated(false);
isAuthenticated();
}

@Test
public void getName() throws Exception {
assertEquals(PRINCIPAL, auth.getName());
}

}

0 comments on commit 662f654

Please sign in to comment.