/* * Copyright 2014-2016 Groupon, Inc * Copyright 2014-2016 The Billing Project, LLC * * The Billing Project 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.killbill.billing.util.security.shiro.realm; import java.util.List; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.support.DelegatingSubject; import org.apache.shiro.util.ThreadContext; import org.killbill.billing.ErrorCode; import org.killbill.billing.security.Permission; import org.killbill.billing.security.SecurityApiException; import org.killbill.billing.util.UtilTestSuiteWithEmbeddedDB; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; public class TestKillBillJdbcRealm extends UtilTestSuiteWithEmbeddedDB { private SecurityManager securityManager; @Override @BeforeMethod(groups = "slow") public void beforeMethod() throws Exception { super.beforeMethod(); final KillBillJdbcRealm realm = new KillBillJdbcRealm(helper.getDataSource(), securityConfig); securityManager = new DefaultSecurityManager(realm); SecurityUtils.setSecurityManager(securityManager); } @AfterMethod(groups = "slow") public void afterMethod() throws Exception { super.afterMethod(); ThreadContext.unbindSecurityManager(); } @Test(groups = "slow") public void testAuthentication() throws SecurityApiException { final String username = "toto"; final String password = "supperCompli43cated"; securityApi.addRoleDefinition("root", ImmutableList.of("*"), callContext); securityApi.addUserRoles(username, password, ImmutableList.of("root"), callContext); final DelegatingSubject subject = new DelegatingSubject(securityManager); final AuthenticationToken goodToken = new UsernamePasswordToken(username, password); securityManager.login(subject, goodToken); Assert.assertTrue(true); try { final AuthenticationToken badToken = new UsernamePasswordToken(username, "somethingelse"); securityManager.login(subject, badToken); Assert.assertTrue(true); securityManager.logout(subject); securityManager.login(subject, badToken); Assert.fail("Should not succeed to login with an incorrect password"); } catch (final AuthenticationException e) { } // Update password and try again final String newPassword = "suppersimple"; securityApi.updateUserPassword(username, newPassword, callContext); try { final AuthenticationToken notGoodTokenAnyLonger = goodToken; securityManager.login(subject, notGoodTokenAnyLonger); Assert.fail("Should not succeed to login with an incorrect password"); } catch (final AuthenticationException e) { } final AuthenticationToken newGoodToken = new UsernamePasswordToken(username, newPassword); securityManager.login(subject, newGoodToken); Assert.assertTrue(true); securityManager.logout(subject); securityApi.invalidateUser(username, callContext); try { final AuthenticationToken notGoodTokenAnyLonger = goodToken; securityManager.login(subject, notGoodTokenAnyLonger); Assert.fail("Should not succeed to login with an incorrect password"); } catch (final AuthenticationException e) { } } @Test(groups = "slow") public void testEmptyPermissions() throws SecurityApiException { securityApi.addRoleDefinition("sanity1", null, callContext); validateUserRoles(securityApi.getRoleDefinition("sanity1", callContext), ImmutableList.<String>of()); securityApi.addRoleDefinition("sanity2", ImmutableList.<String>of(), callContext); validateUserRoles(securityApi.getRoleDefinition("sanity2", callContext), ImmutableList.<String>of()); } @Test(groups = "slow") public void testInvalidPermissions() { testInvalidPermissionScenario(ImmutableList.of("foo")); testInvalidPermissionScenario(ImmutableList.of("account:garbage")); testInvalidPermissionScenario(ImmutableList.of("tag:delete_tag_definition", "account:hsgdsgdjsgd")); testInvalidPermissionScenario(ImmutableList.of("account:credit:vvvv")); } @Test(groups = "slow") public void testSanityOfPermissions() throws SecurityApiException { securityApi.addRoleDefinition("sanity1", ImmutableList.of("account:*", "*"), callContext); validateUserRoles(securityApi.getRoleDefinition("sanity1", callContext), ImmutableList.of("*")); securityApi.addRoleDefinition("sanity2", ImmutableList.of("account:charge", "account:charge"), callContext); validateUserRoles(securityApi.getRoleDefinition("sanity2", callContext), ImmutableList.of("account:charge")); securityApi.addRoleDefinition("sanity3", ImmutableList.of("account:charge", "account:credit", "account:*", "invoice:*"), callContext); validateUserRoles(securityApi.getRoleDefinition("sanity3", callContext), ImmutableList.of("account:*", "invoice:*")); } @Test(groups = "slow") public void testAuthorization() throws SecurityApiException { final String username = "i like"; final String password = "c0ff33"; securityApi.addRoleDefinition("restricted", ImmutableList.of("account:*", "invoice", "tag:create_tag_definition"), callContext); securityApi.addUserRoles(username, password, ImmutableList.of("restricted"), callContext); final AuthenticationToken goodToken = new UsernamePasswordToken(username, password); final Subject subject = securityManager.login(null, goodToken); subject.checkPermission(Permission.ACCOUNT_CAN_CHARGE.toString()); subject.checkPermission(Permission.INVOICE_CAN_CREDIT.toString()); subject.checkPermission(Permission.TAG_CAN_CREATE_TAG_DEFINITION.toString()); try { subject.checkPermission(Permission.TAG_CAN_DELETE_TAG_DEFINITION.toString()); Assert.fail("Subject should not have rights to delete tag definitions"); } catch (AuthorizationException e) { } subject.logout(); securityApi.addRoleDefinition("newRestricted", ImmutableList.of("account:*", "invoice", "tag:delete_tag_definition"), callContext); securityApi.updateUserRoles(username, ImmutableList.of("newRestricted"), callContext); final Subject newSubject = securityManager.login(null, goodToken); newSubject.checkPermission(Permission.ACCOUNT_CAN_CHARGE.toString()); newSubject.checkPermission(Permission.INVOICE_CAN_CREDIT.toString()); newSubject.checkPermission(Permission.TAG_CAN_DELETE_TAG_DEFINITION.toString()); try { newSubject.checkPermission(Permission.TAG_CAN_CREATE_TAG_DEFINITION.toString()); Assert.fail("Subject should not have rights to create tag definitions"); } catch (AuthorizationException e) { } } private void testInvalidPermissionScenario(final List<String> permissions) { try { securityApi.addRoleDefinition("failed", permissions, callContext); Assert.fail("Should fail permissions " + permissions + " were invalid"); } catch (SecurityApiException expected) { Assert.assertEquals(expected.getCode(), ErrorCode.SECURITY_INVALID_PERMISSIONS.getCode()); } } private void validateUserRoles(final List<String> roles, final List<String> expectedRoles) { Assert.assertEquals(roles.size(), expectedRoles.size()); for (final String cur : expectedRoles) { boolean found = false; if (Iterables.tryFind(roles, new Predicate<String>() { @Override public boolean apply(final String input) { return input.equals(cur); } }).orNull() != null) { found = true; } Assert.assertTrue(found, "Cannot find role " + cur); } } }