/*******************************************************************************
* 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.oauth;
import org.cloudfoundry.identity.uaa.approval.Approval;
import org.cloudfoundry.identity.uaa.approval.ApprovalStore;
import org.cloudfoundry.identity.uaa.approval.JdbcApprovalStore;
import org.cloudfoundry.identity.uaa.resources.QueryableResourceManager;
import org.cloudfoundry.identity.uaa.test.JdbcTestBase;
import org.cloudfoundry.identity.uaa.test.TestUtils;
import org.cloudfoundry.identity.uaa.test.UaaTestAccounts;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import static java.util.Collections.singleton;
import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.APPROVED;
import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.DENIED;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
public class UserManagedAuthzApprovalHandlerTests extends JdbcTestBase {
private final UserManagedAuthzApprovalHandler handler = new UserManagedAuthzApprovalHandler();
private UaaTestAccounts testAccounts = UaaTestAccounts.standard(null);
private ApprovalStore approvalStore = null;
private String userId = null;
private TestAuthentication userAuthentication;
@Before
public void initUserManagedAuthzApprovalHandlerTests() {
approvalStore = new JdbcApprovalStore(jdbcTemplate);
handler.setApprovalStore(approvalStore);
handler.setClientDetailsService(
mockClientDetailsService(
"foo",
new String[]{
"cloud_controller.read",
"cloud_controller.write",
"openid",
"space.*.developer"
},
Collections.emptySet()
)
);
userId = new RandomValueStringGenerator().generate();
testAccounts.addRandomUser(jdbcTemplate, userId);
userAuthentication = new TestAuthentication(userId, testAccounts.getUserName(), true);
}
private QueryableResourceManager<ClientDetails> mockClientDetailsService(String id, String[] scope, Set<String> autoApprovedScopes) {
@SuppressWarnings("unchecked")
QueryableResourceManager<ClientDetails> service = mock(QueryableResourceManager.class);
BaseClientDetails details = mock(BaseClientDetails.class);
Mockito.when(service.retrieve(id)).thenReturn(details);
Mockito.when(details.getScope()).thenReturn(new HashSet<>(Arrays.asList(scope)));
Mockito.when(details.getAutoApproveScopes()).thenReturn(autoApprovedScopes);
return service;
}
@Test
public void testNoScopeApproval() {
AuthorizationRequest request = new AuthorizationRequest("testclient", Collections.<String>emptySet());
request.setApproved(true);
// The request is approved but does not request any scopes. The user has
// also not approved any scopes. Approved.
assertTrue(handler.isApproved(request, userAuthentication));
}
@Test
public void testNoPreviouslyApprovedScopes() {
AuthorizationRequest request = new AuthorizationRequest(
"foo",
new HashSet<>(
Arrays.asList("cloud_controller.read", "cloud_controller.write")
)
);
request.setApproved(false);
// The request needs user approval for scopes. The user has also not
// approved any scopes prior to this request.
// Not approved.
assertFalse(handler.isApproved(request, userAuthentication));
}
@Test
public void testAuthzApprovedButNoPreviouslyApprovedScopes() {
AuthorizationRequest request = new AuthorizationRequest(
"foo",
new HashSet<>(
Arrays.asList("cloud_controller.read", "cloud_controller.write")
)
);
request.setApproved(true);
// The request needs user approval for scopes. The user has also not
// approved any scopes prior to this request.
// Not approved.
assertFalse(handler.isApproved(request, userAuthentication));
}
@Test
public void testNoRequestedScopesButSomeApprovedScopes() {
AuthorizationRequest request = new AuthorizationRequest("foo", new HashSet<String>());
request.setApproved(false);
long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000);
Date nextWeek = new Date(theFuture);
approvalStore.addApproval(new Approval()
.setUserId(userAuthentication.getId())
.setClientId("foo")
.setScope("cloud_controller.read")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userAuthentication.getId())
.setClientId("foo")
.setScope("cloud_controller.write")
.setExpiresAt(nextWeek)
.setStatus(DENIED));
// The request is approved because the user has not requested any scopes
assertTrue(handler.isApproved(request, userAuthentication));
assertEquals(0, request.getScope().size());
}
@Test
public void testRequestedScopesDontMatchApprovalsAtAll() {
AuthorizationRequest request = new AuthorizationRequest(
"foo",
new HashSet<>(
Arrays.asList("openid")
)
);
request.setApproved(false);
long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000);
Date nextWeek = new Date(theFuture);
approvalStore.addApproval(new Approval()
.setUserId(userAuthentication.getId())
.setClientId("foo")
.setScope("cloud_controller.read")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userAuthentication.getId())
.setClientId("foo")
.setScope("cloud_controller.write")
.setExpiresAt(nextWeek)
.setStatus(DENIED));
// The request is not approved because the user has not yet approved the
// scopes requested
assertFalse(handler.isApproved(request, userAuthentication));
}
@Test
public void testOnlySomeRequestedScopeMatchesApproval() {
AuthorizationRequest request = new AuthorizationRequest(
"foo",
new HashSet<>(
Arrays.asList("openid", "cloud_controller.read")
)
);
request.setApproved(false);
long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000);
Date nextWeek = new Date(theFuture);
approvalStore.addApproval(new Approval()
.setUserId(userAuthentication.getId())
.setClientId("foo")
.setScope("cloud_controller.read")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userAuthentication.getId())
.setClientId("foo")
.setScope("cloud_controller.write")
.setExpiresAt(nextWeek)
.setStatus(DENIED));
// The request is not approved because the user has not yet approved all
// the scopes requested
assertFalse(handler.isApproved(request, userAuthentication));
}
@Test
public void testOnlySomeRequestedScopeMatchesDeniedApprovalButScopeAutoApproved() {
AuthorizationRequest request = new AuthorizationRequest(
"foo",
new HashSet<>(
Arrays.asList("openid", "cloud_controller.read")
)
);
request.setApproved(false);
long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000);
Date nextWeek = new Date(theFuture);
handler.setClientDetailsService(
mockClientDetailsService(
"foo",
new String[]{
"cloud_controller.read",
"cloud_controller.write",
"openid"
},
singleton("true")
)
);
approvalStore.addApproval(new Approval()
.setUserId(userAuthentication.getId())
.setClientId("foo")
.setScope("cloud_controller.read")
.setExpiresAt(nextWeek)
.setStatus(DENIED));
approvalStore.addApproval(new Approval()
.setUserId(userAuthentication.getId())
.setClientId("foo")
.setScope("openid")
.setExpiresAt(nextWeek)
.setStatus(DENIED));
assertTrue(handler.isApproved(request, userAuthentication));
assertEquals(new HashSet<>(Arrays.asList(new String[]{"cloud_controller.read", "openid"})),request.getScope());
}
@Test
public void testRequestedScopesMatchApprovalButAdditionalScopesRequested() {
AuthorizationRequest request = new AuthorizationRequest(
"foo",
new HashSet<>(
Arrays.asList(
"openid",
"cloud_controller.read",
"cloud_controller.write"
)
)
);
request.setApproved(false);
long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000);
Date nextWeek = new Date(theFuture);
approvalStore.addApproval(new Approval()
.setUserId(userAuthentication.getId())
.setClientId("foo")
.setScope("cloud_controller.read")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userAuthentication.getId())
.setClientId("foo")
.setScope("cloud_controller.write")
.setExpiresAt(nextWeek)
.setStatus(DENIED));
// The request is not approved because the user has not yet approved all
// the scopes requested
assertFalse(handler.isApproved(request, userAuthentication));
}
@Test
public void testAllRequestedScopesMatchApproval() {
AuthorizationRequest request = new AuthorizationRequest(
"foo",
new HashSet<>(
Arrays.asList(
"openid",
"cloud_controller.read",
"cloud_controller.write"
)
)
);
request.setApproved(false);
long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000);
Date nextWeek = new Date(theFuture);
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("openid")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("cloud_controller.read")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("cloud_controller.write")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
// The request is approved because the user has approved all the scopes
// requested
assertTrue(handler.isApproved(request, userAuthentication));
assertEquals(new HashSet<>(Arrays.asList(new String[]{"openid", "cloud_controller.read","cloud_controller.write"})), request.getScope());
}
@Test
public void testRequestedScopesMatchApprovalButSomeDenied() {
AuthorizationRequest request = new AuthorizationRequest(
"foo",
new HashSet<>(
Arrays.asList(
"openid",
"cloud_controller.read",
"cloud_controller.write"
)
)
);
request.setApproved(false);
long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000);
Date nextWeek = new Date(theFuture);
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("openid")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("cloud_controller.read")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("cloud_controller.write")
.setExpiresAt(nextWeek)
.setStatus(DENIED));
// The request is approved because the user has acted on all requested
// scopes
assertTrue(handler.isApproved(request, userAuthentication));
assertEquals(new HashSet<>(Arrays.asList(new String[]{"openid", "cloud_controller.read"})),request.getScope());
}
@Test
public void testRequestedScopesMatchApprovalSomeDeniedButDeniedScopesAutoApproved() {
AuthorizationRequest request = new AuthorizationRequest(
"foo",
new HashSet<>(
Arrays.asList(
"openid",
"cloud_controller.read",
"cloud_controller.write"
)
)
);
request.setApproved(false);
long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000);
Date nextWeek = new Date(theFuture);
handler.setClientDetailsService(mockClientDetailsService(
"foo",
new String[]{
"cloud_controller.read",
"cloud_controller.write",
"openid"
},
singleton("cloud_controller.write")));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("openid")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("cloud_controller.read")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("cloud_controller.write")
.setExpiresAt(nextWeek)
.setStatus(DENIED));
// The request is not approved because the user has denied some of the
// scopes requested
assertTrue(handler.isApproved(request, userAuthentication));
assertThat(
request.getScope(),
Matchers.containsInAnyOrder(new String[]{"openid", "cloud_controller.read","cloud_controller.write"})
);
}
@Test
public void testRequestedScopesMatchApprovalSomeDeniedButDeniedScopesAutoApprovedByWildcard() {
AuthorizationRequest request = new AuthorizationRequest(
"foo",
new HashSet<>(
Arrays.asList(
"openid",
"cloud_controller.read",
"cloud_controller.write",
"space.1.developer",
"space.2.developer"
)
)
);
request.setApproved(false);
long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000);
Date nextWeek = new Date(theFuture);
Set<String> autoApprovedScopes = new HashSet<>();
autoApprovedScopes.add("space.*.developer");
autoApprovedScopes.add("cloud_controller.write");
handler.setClientDetailsService(mockClientDetailsService(
"foo",
new String[]{
"cloud_controller.read",
"cloud_controller.write",
"openid",
"space.*.developer"
},autoApprovedScopes));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("openid")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("cloud_controller.read")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("cloud_controller.write")
.setExpiresAt(nextWeek)
.setStatus(DENIED));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("space.1.developer")
.setExpiresAt(nextWeek)
.setStatus(DENIED));
// The request is not approved because the user has denied some of the
// scopes requested
assertTrue(handler.isApproved(request, userAuthentication));
assertThat(
request.getScope(),
Matchers.containsInAnyOrder(new String[]{"openid", "cloud_controller.read","cloud_controller.write","space.1.developer", "space.2.developer"})
);
}
@Test
public void testRequestedScopesMatchByWildcard() {
AuthorizationRequest request = new AuthorizationRequest(
"foo",
new HashSet<>(
Arrays.asList(
"openid",
"cloud_controller.read",
"cloud_controller.write",
"space.1.developer"
)
)
);
request.setApproved(false);
long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000);
Date nextWeek = new Date(theFuture);
handler.setClientDetailsService(mockClientDetailsService(
"foo",
new String[]{
"cloud_controller.read",
"cloud_controller.write",
"openid",
"space.*.developer"
},
singleton("true")));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("openid")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("cloud_controller.read")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("cloud_controller.write")
.setExpiresAt(nextWeek)
.setStatus(DENIED));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("space.1.developer")
.setExpiresAt(nextWeek)
.setStatus(DENIED));
// The request is not approved because the user has denied some of the
// scopes requested
assertTrue(handler.isApproved(request, userAuthentication));
assertThat(
request.getScope(),
Matchers.containsInAnyOrder(new String[]{"openid", "cloud_controller.read","cloud_controller.write","space.1.developer"})
);
}
@Test
public void testSomeRequestedScopesMatchApproval() {
AuthorizationRequest request = new AuthorizationRequest(
"foo",
new HashSet<>(Arrays.asList("openid"))
);
request.setApproved(false);
long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000);
Date nextWeek = new Date(theFuture);
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("openid")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("cloud_controller.read")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
approvalStore.addApproval(new Approval()
.setUserId(userId)
.setClientId("foo")
.setScope("cloud_controller.write")
.setExpiresAt(nextWeek)
.setStatus(APPROVED));
// The request is approved because the user has approved all the scopes
// requested
assertTrue(handler.isApproved(request, userAuthentication));
assertEquals(new HashSet<>(Arrays.asList(new String[]{"openid"})), request.getScope());
}
@After
public void cleanupDataSource() throws Exception {
TestUtils.deleteFrom(dataSource, "authz_approvals");
assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals", Integer.class), is(0));
}
@SuppressWarnings("serial")
protected static class TestAuthentication extends AbstractAuthenticationToken {
private final String principal;
private String name;
private String id;
public TestAuthentication(String id, String name, boolean authenticated) {
super(null);
setAuthenticated(authenticated);
this.principal = name;
this.name = name;
this.id = id;
}
@Override
public Object getCredentials() {
return null;
}
@Override
public String getPrincipal() {
return this.principal;
}
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
}
}