/*
* 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);
}
}
}