killbill-memoizeit
Changes
beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java 17(+15 -2)
pom.xml 5(+3 -2)
profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java 3(+3 -0)
profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java 13(+8 -5)
profiles/killbill/src/test/java/org/killbill/billing/server/security/TestKillbillJdbcTenantRealm.java 5(+2 -3)
util/pom.xml 4(+4 -0)
util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsModelDao.java 114(+114 -0)
util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java 41(+41 -0)
util/src/main/java/org/killbill/billing/util/security/shiro/KillbillCredentialsMatcher.java 2(+1 -1)
util/src/main/resources/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.sql.stg 63(+63 -0)
util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.sql.stg 70(+70 -0)
util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java 203(+203 -0)
Details
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java
index f25423a..1142ba6 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/BeatrixIntegrationModule.java
@@ -57,6 +57,8 @@ import org.killbill.billing.util.glue.NonEntityDaoModule;
import org.killbill.billing.util.glue.RecordIdModule;
import org.killbill.billing.util.glue.SecurityModule;
import org.killbill.billing.util.glue.TagStoreModule;
+import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
+import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
public class BeatrixIntegrationModule extends KillBillModule {
@@ -95,7 +97,7 @@ public class BeatrixIntegrationModule extends KillBillModule {
install(new RecordIdModule(configSource));
install(new UsageModule(configSource));
install(new SecurityModule(configSource));
- install(new KillBillShiroModule(configSource));
+ install(new KillBillShiroModuleOnlyIniRealm(configSource));
install(new BeatrixModule(configSource));
bind(AccountChecker.class).asEagerSingleton();
@@ -104,7 +106,6 @@ public class BeatrixIntegrationModule extends KillBillModule {
bind(PaymentChecker.class).asEagerSingleton();
bind(RefundChecker.class).asEagerSingleton();
bind(AuditChecker.class).asEagerSingleton();
-
bind(TestApiListener.class).asEagerSingleton();
}
@@ -130,4 +131,16 @@ public class BeatrixIntegrationModule extends KillBillModule {
install(new MockPaymentProviderPluginModule(NON_OSGI_PLUGIN_NAME, TestIntegrationBase.getClock(), configSource));
}
}
+
+ private static class KillBillShiroModuleOnlyIniRealm extends KillBillShiroModule {
+
+ public KillBillShiroModuleOnlyIniRealm(final KillbillConfigSource configSource) {
+ super(configSource);
+ }
+ protected void configureJDBCRealm() {
+ }
+ protected void configureLDAPRealm() {
+ }
+
+ }
}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/RoleDefinitionJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/RoleDefinitionJson.java
new file mode 100644
index 0000000..1f4c8a6
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/RoleDefinitionJson.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.jaxrs.json;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class RoleDefinitionJson {
+
+ private final String role;
+ private final List<String> permissions;
+
+ @JsonCreator
+ public RoleDefinitionJson(@JsonProperty("role") final String role,
+ @JsonProperty("permissions") final List<String> permissions) {
+ this.role = role;
+ this.permissions = permissions;
+ }
+
+ public String getRole() {
+ return role;
+ }
+
+ public List<String> getPermissions() {
+ return permissions;
+ }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/UserRolesJson.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/UserRolesJson.java
new file mode 100644
index 0000000..af6ab2f
--- /dev/null
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/json/UserRolesJson.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.jaxrs.json;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class UserRolesJson {
+
+ private final String username;
+ private final String password;
+ private final List<String> roles;
+
+ @JsonCreator
+ public UserRolesJson(@JsonProperty("username") final String username,
+ @JsonProperty("password") final String password,
+ @JsonProperty("roles") final List<String> roles) {
+ this.username = username;
+ this.password = password;
+ this.roles = roles;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public List<String> getRoles() {
+ return roles;
+ }
+}
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
index 5e78159..66d1b43 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/PluginResource.java
@@ -79,7 +79,7 @@ public class PluginResource extends JaxRsResourceBase {
private final HttpServlet osgiServlet;
@Inject
- public PluginResource(@Named("osgi") final HttpServlet osgiServlet,
+ public PluginResource(@Named("osgi") final HttpServlet osgiServlet, // See DefaultOSGIModule.OSGI_NAMED
final JaxrsUriBuilder uriBuilder,
final TagUserApi tagUserApi,
final CustomFieldUserApi customFieldUserApi,
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
index 0330c54..33f734f 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/SecurityResource.java
@@ -21,27 +21,36 @@ import java.util.Set;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
-
import org.killbill.billing.account.api.AccountUserApi;
-import org.killbill.billing.payment.api.PaymentApi;
-import org.killbill.billing.util.callcontext.TenantContext;
-import org.killbill.clock.Clock;
+import org.killbill.billing.jaxrs.json.RoleDefinitionJson;
import org.killbill.billing.jaxrs.json.SubjectJson;
+import org.killbill.billing.jaxrs.json.UserRolesJson;
import org.killbill.billing.jaxrs.util.Context;
import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
+import org.killbill.billing.payment.api.PaymentApi;
import org.killbill.billing.security.Permission;
+import org.killbill.billing.security.SecurityApiException;
import org.killbill.billing.security.api.SecurityApi;
import org.killbill.billing.util.api.AuditUserApi;
import org.killbill.billing.util.api.CustomFieldUserApi;
import org.killbill.billing.util.api.TagUserApi;
+import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.clock.Clock;
import com.codahale.metrics.annotation.Timed;
import com.google.common.base.Functions;
@@ -100,4 +109,89 @@ public class SecurityResource extends JaxRsResourceBase {
final SubjectJson subjectJson = new SubjectJson(subject);
return Response.status(Status.OK).entity(subjectJson).build();
}
+
+ @Timed
+ @POST
+ @Path("/users")
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Add a new user with roles (to make api requests)")
+ public Response addUserRoles(final UserRolesJson json,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws SecurityApiException {
+ securityApi.addUserRoles(json.getUsername(), json.getPassword(), json.getRoles(), context.createContext(createdBy, reason, comment, request));
+ return Response.status(Status.CREATED).build();
+ }
+
+ @Timed
+ @PUT
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @Path("/users/{username:" + ANYTHING_PATTERN + "}/password")
+ @ApiOperation(value = "Update a user password")
+ public Response updateUserPassword(final UserRolesJson json,
+ @PathParam("username") final String username,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws SecurityApiException {
+ securityApi.updateUserPassword(username, json.getPassword(), context.createContext(createdBy, reason, comment, request));
+ return Response.status(Status.OK).build();
+ }
+
+
+ @Timed
+ @PUT
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @Path("/users/{username:" + ANYTHING_PATTERN + "}/roles")
+ @ApiOperation(value = "Update roles associated to a user")
+ public Response updateUserRoles(final UserRolesJson json,
+ @PathParam("username") final String username,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws SecurityApiException {
+ securityApi.updateUserRoles(username, json.getRoles(), context.createContext(createdBy, reason, comment, request));
+ return Response.status(Status.OK).build();
+ }
+
+ @Timed
+ @DELETE
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @Path("/users/{username:" + ANYTHING_PATTERN + "}")
+ @ApiOperation(value = "Invalidate an existing user")
+ public Response invalidateUser(@PathParam("username") final String username,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws SecurityApiException {
+ securityApi.invalidateUser(username, context.createContext(createdBy, reason, comment, request));
+ return Response.status(Status.NO_CONTENT).build();
+ }
+
+
+
+ @Timed
+ @POST
+ @Path("/roles")
+ @Consumes(APPLICATION_JSON)
+ @Produces(APPLICATION_JSON)
+ @ApiOperation(value = "Add a new role definition)")
+ public Response addRoleDefinition(final RoleDefinitionJson json,
+ @HeaderParam(HDR_CREATED_BY) final String createdBy,
+ @HeaderParam(HDR_REASON) final String reason,
+ @HeaderParam(HDR_COMMENT) final String comment,
+ @javax.ws.rs.core.Context final HttpServletRequest request,
+ @javax.ws.rs.core.Context final UriInfo uriInfo) throws SecurityApiException {
+ securityApi.addRoleDefinition(json.getRole(), json.getPermissions(), context.createContext(createdBy, reason, comment, request));
+ return Response.status(Status.CREATED).build();
+ }
}
pom.xml 5(+3 -2)
diff --git a/pom.xml b/pom.xml
index 544dcd9..84a6e30 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2010-2013 Ning, Inc.
- ~ Copyright 2014 Groupon, Inc
+ ~ Copyright 2014-2015 Groupon, Inc
+ ~ Copyright 2014-2015 The Billing Project, LLC
~
- ~ Groupon licenses this file to you under the Apache License, version 2.0
+ ~ 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:
~
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
index 113ed01..0617b12 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
@@ -36,6 +36,7 @@ import org.killbill.billing.util.glue.IniRealmProvider;
import org.killbill.billing.util.glue.JDBCSessionDaoProvider;
import org.killbill.billing.util.glue.KillBillShiroModule;
import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
+import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
import org.skife.config.ConfigSource;
import org.skife.config.ConfigurationObjectFactory;
@@ -61,6 +62,8 @@ public class KillBillShiroWebModule extends ShiroWebModule {
bindRealm().toProvider(IniRealmProvider.class).asEagerSingleton();
+ bindRealm().to(KillBillJdbcRealm.class).asEagerSingleton();
+
if (KillBillShiroModule.isLDAPEnabled()) {
bindRealm().to(KillBillJndiLdapRealm.class).asEagerSingleton();
}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java b/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java
index 242de3c..4fb769e 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/security/TenantFilter.java
@@ -40,7 +40,7 @@ import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;
import org.killbill.billing.jaxrs.resources.JaxrsResource;
import org.killbill.billing.server.listeners.KillbillGuiceListener;
-import org.killbill.billing.server.modules.KillbillServerModule;
+import org.killbill.billing.server.modules.KillbillPlatformModule;
import org.killbill.billing.tenant.api.Tenant;
import org.killbill.billing.tenant.api.TenantApiException;
import org.killbill.billing.tenant.api.TenantUserApi;
@@ -61,17 +61,17 @@ public class TenantFilter implements Filter {
protected TenantUserApi tenantUserApi;
@Inject
- @Named(KillbillServerModule.SHIRO_DATA_SOURCE_ID)
+ @Named(KillbillPlatformModule.SHIRO_DATA_SOURCE_ID_NAMED)
protected DataSource dataSource;
private ModularRealmAuthenticator modularRealmAuthenticator;
@Override
public void init(final FilterConfig filterConfig) throws ServletException {
- final Realm killbillJdbcRealm = new KillbillJdbcRealm(dataSource);
+ final Realm killbillJdbcTenantRealm = new KillbillJdbcTenantRealm(dataSource);
// We use Shiro to verify the api credentials - but the Shiro Subject is only used for RBAC
modularRealmAuthenticator = new ModularRealmAuthenticator();
- modularRealmAuthenticator.setRealms(ImmutableList.<Realm>of(killbillJdbcRealm));
+ modularRealmAuthenticator.setRealms(ImmutableList.<Realm>of(killbillJdbcTenantRealm));
}
@Override
diff --git a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestSecurity.java b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestSecurity.java
index e3fb516..b7d67d8 100644
--- a/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestSecurity.java
+++ b/profiles/killbill/src/test/java/org/killbill/billing/jaxrs/TestSecurity.java
@@ -18,6 +18,7 @@
package org.killbill.billing.jaxrs;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -25,11 +26,18 @@ import javax.annotation.Nullable;
import javax.ws.rs.core.Response.Status;
import org.killbill.billing.client.KillBillClientException;
+import org.killbill.billing.client.model.Permissions;
+import org.killbill.billing.client.model.RoleDefinition;
+import org.killbill.billing.client.model.UserRoles;
import org.killbill.billing.security.Permission;
import org.testng.Assert;
import org.testng.annotations.Test;
+import com.ning.http.client.Response;
+
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.io.Resources;
public class TestSecurity extends TestJaxrsBase {
@@ -55,6 +63,146 @@ public class TestSecurity extends TestJaxrsBase {
Assert.assertEquals(new HashSet<String>(stephanesPermissions), ImmutableSet.<String>of(Permission.PAYMENT_CAN_REFUND.toString()));
}
+ @Test(groups = "slow")
+ public void testDynamicUserRolesAllPermissions() throws Exception {
+ testDynamicUserRolesInternal("wqeqwe", "jdsh763s", "all", "*", true);
+ }
+
+ @Test(groups = "slow")
+ public void testDynamicUserRolesAllCatalogPermissions() throws Exception {
+ testDynamicUserRolesInternal("wqeqsdswe", "jsddsh763s", "allcatalog", "catalog:*", true);
+ }
+
+ @Test(groups = "slow")
+ public void testDynamicUserRolesCorrectCatalogPermissions() throws Exception {
+ testDynamicUserRolesInternal("wqeq23f6we", "jds5gh763s", "correctcatalog", "catalog:config_upload", true);
+ }
+
+ @Test(groups = "slow")
+ public void testDynamicUserRolesIncorrectPermissions() throws Exception {
+ testDynamicUserRolesInternal("wqsdeqwe", "jd23fsh63s", "incorrect", "account:*", false);
+ }
+
+ @Test(groups = "slow")
+ public void testUserPermission() throws KillBillClientException {
+
+ final String roleDefinition = "notEnoughToAddUserAndRoles";
+
+ final List<String> permissions = new ArrayList<String>();
+ for (Permission cur : Permission.values()) {
+ if (!cur.getGroup().equals("user")) {
+ permissions.add(cur.toString());
+ }
+ }
+ Response response = killBillClient.addRoleDefinition(new RoleDefinition(roleDefinition, permissions), createdBy, reason, comment);
+ Assert.assertEquals(response.getStatusCode(), 201);
+
+ final String username = "candy";
+ final String password = "lolipop";
+ response = killBillClient.addUserRoles(new UserRoles(username, password, ImmutableList.of(roleDefinition)), createdBy, reason, comment);
+ Assert.assertEquals(response.getStatusCode(), 201);
+
+ // Now 'login' as new user (along with roles to make an API call requiring permissions), and check behavior
+ logout();
+ login(username, password);
+
+ boolean success = false;
+ try {
+ killBillClient.addRoleDefinition(new RoleDefinition("dsfdsfds", ImmutableList.of("*")), createdBy, reason, comment);
+ success = true;
+ } catch (final Exception e) {
+ } finally {
+ Assert.assertFalse(success);
+ }
+
+ success = false;
+ try {
+ killBillClient.addUserRoles(new UserRoles("sdsd", "sdsdsd", ImmutableList.of(roleDefinition)), createdBy, reason, comment);
+ success = true;
+ } catch (final Exception e) {
+ } finally {
+ Assert.assertFalse(success);
+ }
+ }
+
+ @Test(groups = "slow")
+ public void testUserWithUpdates() throws KillBillClientException {
+
+ final String roleDefinition = "somethingNice";
+ final String allPermissions = "*";
+
+ final String username = "GuanYu";
+ final String password = "IamAGreatWarrior";
+
+ Response response = killBillClient.addRoleDefinition(new RoleDefinition(roleDefinition, ImmutableList.of(allPermissions)), createdBy, reason, comment);
+ Assert.assertEquals(response.getStatusCode(), 201);
+
+ response = killBillClient.addUserRoles(new UserRoles(username, password, ImmutableList.of(roleDefinition)), createdBy, reason, comment);
+ Assert.assertEquals(response.getStatusCode(), 201);
+
+ logout();
+ login(username, password);
+ Permissions permissions = killBillClient.getPermissions();
+ Assert.assertEquals(permissions.size(), Permission.values().length);
+
+ String newPassword = "IamTheBestWarrior";
+ killBillClient.updateUserPassword(username, newPassword, createdBy, reason, comment);
+
+ logout();
+ login(username, newPassword);
+ permissions = killBillClient.getPermissions();
+ Assert.assertEquals(permissions.size(), Permission.values().length);
+
+ final String newRoleDefinition = "somethingLessNice";
+ // Only enough permissions to invalidate itself in the last step...
+ final String littlePermissions = "user";
+
+ response = killBillClient.addRoleDefinition(new RoleDefinition(newRoleDefinition, ImmutableList.of(littlePermissions)), createdBy, reason, comment);
+ Assert.assertEquals(response.getStatusCode(), 201);
+
+ killBillClient.updateUserRoles(username, ImmutableList.of(newRoleDefinition), createdBy, reason, comment);
+ permissions = killBillClient.getPermissions();
+ // This will only work if correct shiro cache invalidation was performed... requires lots of sweat to get it to work ;-)
+ Assert.assertEquals(permissions.size(), 2);
+
+ killBillClient.invalidateUser(username, createdBy, reason, comment);
+ try {
+ killBillClient.getPermissions();
+ Assert.fail();
+ } catch (final KillBillClientException e) {
+ Assert.assertEquals(e.getResponse().getStatusCode(), Status.UNAUTHORIZED.getStatusCode());
+ }
+
+
+ }
+
+ private void testDynamicUserRolesInternal(final String username, final String password, final String roleDefinition, final String permissions, final boolean expectPermissionSuccess) throws Exception {
+
+ Response response = killBillClient.addRoleDefinition(new RoleDefinition(roleDefinition, ImmutableList.of(permissions)), createdBy, reason, comment);
+ Assert.assertEquals(response.getStatusCode(), 201);
+
+ response = killBillClient.addUserRoles(new UserRoles(username, password, ImmutableList.of(roleDefinition)), createdBy, reason, comment);
+ Assert.assertEquals(response.getStatusCode(), 201);
+
+ // Now 'login' as new user (along with roles to make an API call requiring permissions), and check behavior
+ logout();
+ login(username, password);
+
+ boolean success = false;
+ try {
+ final String catalogPath = Resources.getResource("SpyCarBasic.xml").getPath();
+ killBillClient.uploadXMLCatalog(catalogPath, createdBy, reason, comment);
+ success = true;
+ } catch (final Exception e) {
+ if (expectPermissionSuccess ||
+ !e.getMessage().startsWith("java.lang.IllegalArgumentException: Unauthorized")) {
+ throw e;
+ }
+ } finally {
+ Assert.assertTrue(success == expectPermissionSuccess);
+ }
+ }
+
private List<String> getPermissions(@Nullable final String username, @Nullable final String password) throws Exception {
login(username, password);
return killBillClient.getPermissions();
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java b/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java
index fae6f7f..a401574 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/dao/DefaultTenantDao.java
@@ -33,7 +33,7 @@ import org.skife.jdbi.v2.IDBI;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.tenant.api.Tenant;
import org.killbill.billing.tenant.api.TenantApiException;
-import org.killbill.billing.tenant.security.KillbillCredentialsMatcher;
+import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher;
import org.killbill.billing.util.cache.CacheControllerDispatcher;
import org.killbill.billing.callcontext.InternalCallContext;
import org.killbill.billing.callcontext.InternalTenantContext;
diff --git a/tenant/src/test/java/org/killbill/billing/tenant/dao/TestDefaultTenantDao.java b/tenant/src/test/java/org/killbill/billing/tenant/dao/TestDefaultTenantDao.java
index ddba113..d23e86a 100644
--- a/tenant/src/test/java/org/killbill/billing/tenant/dao/TestDefaultTenantDao.java
+++ b/tenant/src/test/java/org/killbill/billing/tenant/dao/TestDefaultTenantDao.java
@@ -23,12 +23,11 @@ import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.testng.Assert;
-import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.killbill.billing.tenant.TenantTestSuiteWithEmbeddedDb;
import org.killbill.billing.tenant.api.DefaultTenant;
-import org.killbill.billing.tenant.security.KillbillCredentialsMatcher;
+import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher;
public class TestDefaultTenantDao extends TenantTestSuiteWithEmbeddedDb {
util/pom.xml 4(+4 -0)
diff --git a/util/pom.xml b/util/pom.xml
index 6bb4fc1..eb59185 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -133,6 +133,10 @@
</dependency>
<dependency>
<groupId>org.kill-bill.billing</groupId>
+ <artifactId>killbill-platform-base</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.kill-bill.billing</groupId>
<artifactId>killbill-platform-osgi-api</artifactId>
<scope>test</scope>
</dependency>
diff --git a/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java b/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java
index cb1c9f0..379e3b6 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/IniRealmProvider.java
@@ -33,7 +33,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// Really Provider<IniRealm>, but avoid an extra cast below
-public class IniRealmProvider implements Provider<Realm> {
+public class IniRealmProvider implements Provider<IniRealm> {
private static final Logger log = LoggerFactory.getLogger(IniRealmProvider.class);
@@ -45,7 +45,7 @@ public class IniRealmProvider implements Provider<Realm> {
}
@Override
- public Realm get() {
+ public IniRealm get() {
try {
final Factory<SecurityManager> factory = new IniSecurityManagerFactory(securityConfig.getShiroResourcePath());
// TODO Pierre hack - lame cast here, but we need to have Shiro go through its reflection magic
@@ -53,8 +53,17 @@ public class IniRealmProvider implements Provider<Realm> {
// by going through IniSecurityManagerFactory.
final DefaultSecurityManager securityManager = (DefaultSecurityManager) factory.getInstance();
final Collection<Realm> realms = securityManager.getRealms();
- // Null check mainly for testing
- return realms == null ? new IniRealm(securityConfig.getShiroResourcePath()) : realms.iterator().next();
+ if (realms == null || realms.isEmpty()) {
+ return new IniRealm(securityConfig.getShiroResourcePath());
+ }
+
+ for (final Realm cur : realms) {
+ if (cur instanceof IniRealm) {
+ return (IniRealm) cur;
+ }
+ }
+ throw new ConfigurationException();
+
} catch (final ConfigurationException e) {
log.warn("Unable to configure RBAC", e);
return new IniRealm();
diff --git a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
index f74ffa0..00551eb 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
@@ -26,6 +26,7 @@ import org.apache.shiro.session.mgt.SessionManager;
import org.killbill.billing.platform.api.KillbillConfigSource;
import org.killbill.billing.util.config.RbacConfig;
import org.killbill.billing.util.security.shiro.dao.JDBCSessionDao;
+import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
import org.killbill.billing.util.security.shiro.realm.KillBillJndiLdapRealm;
import org.skife.config.ConfigSource;
import org.skife.config.ConfigurationObjectFactory;
@@ -39,6 +40,7 @@ public class KillBillShiroModule extends ShiroModule {
public static final String KILLBILL_LDAP_PROPERTY = "killbill.server.ldap";
public static final String KILLBILL_RBAC_PROPERTY = "killbill.server.rbac";
+
public static boolean isLDAPEnabled() {
return Boolean.parseBoolean(System.getProperty(KILLBILL_LDAP_PROPERTY, "false"));
}
@@ -64,6 +66,16 @@ public class KillBillShiroModule extends ShiroModule {
bindRealm().toProvider(IniRealmProvider.class).asEagerSingleton();
+ configureJDBCRealm();
+
+ configureLDAPRealm();
+ }
+
+ protected void configureJDBCRealm() {
+ bindRealm().to(KillBillJdbcRealm.class).asEagerSingleton();
+ }
+
+ protected void configureLDAPRealm() {
if (isLDAPEnabled()) {
bindRealm().to(KillBillJndiLdapRealm.class).asEagerSingleton();
}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java b/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java
index 7ad1e93..1f0920c 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/SecurityModule.java
@@ -24,6 +24,8 @@ import org.killbill.billing.util.config.SecurityConfig;
import org.killbill.billing.util.security.api.DefaultSecurityApi;
import org.killbill.billing.util.security.api.DefaultSecurityService;
import org.killbill.billing.util.security.api.SecurityService;
+import org.killbill.billing.util.security.shiro.dao.DefaultUserDao;
+import org.killbill.billing.util.security.shiro.dao.UserDao;
import org.skife.config.ConfigurationObjectFactory;
public class SecurityModule extends KillBillModule {
@@ -34,10 +36,15 @@ public class SecurityModule extends KillBillModule {
public void configure() {
installConfig();
+ installDao();
installSecurityApi();
installSecurityService();
}
+ protected void installDao() {
+ bind(UserDao.class).to(DefaultUserDao.class).asEagerSingleton();
+ }
+
private void installConfig() {
final SecurityConfig securityConfig = new ConfigurationObjectFactory(skifeConfigSource).build(SecurityConfig.class);
bind(SecurityConfig.class).toInstance(securityConfig);
diff --git a/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java b/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java
index 5d0bc2b..f1718c1 100644
--- a/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java
+++ b/util/src/main/java/org/killbill/billing/util/security/api/DefaultSecurityApi.java
@@ -16,29 +16,56 @@
package org.killbill.billing.util.security.api;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
import org.apache.shiro.SecurityUtils;
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.realm.Realm;
+import org.apache.shiro.realm.jdbc.JdbcRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.killbill.billing.ErrorCode;
import org.killbill.billing.security.Logical;
import org.killbill.billing.security.Permission;
import org.killbill.billing.security.SecurityApiException;
import org.killbill.billing.security.api.SecurityApi;
+import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.callcontext.TenantContext;
+import org.killbill.billing.util.security.shiro.dao.RolesPermissionsModelDao;
+import org.killbill.billing.util.security.shiro.dao.UserDao;
+import org.killbill.billing.util.security.shiro.dao.UserRolesModelDao;
+import org.killbill.billing.util.security.shiro.realm.KillBillJdbcRealm;
+import com.google.common.base.Function;
import com.google.common.base.Functions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
public class DefaultSecurityApi implements SecurityApi {
private static final String[] allPermissions = new String[Permission.values().length];
+ private final UserDao userDao;
+
+ @Inject
+ public DefaultSecurityApi(final UserDao userDao) {
+ this.userDao = userDao;
+ }
+
@Override
public synchronized void login(final Object principal, final Object credentials) {
final Subject currentUser = SecurityUtils.getSubject();
@@ -124,6 +151,118 @@ public class DefaultSecurityApi implements SecurityApi {
}
}
+ @Override
+ public void addUserRoles(final String username, final String password, final List<String> roles, final CallContext callContext) throws SecurityApiException {
+ userDao.insertUser(username, password, roles, callContext.getUserName());
+ }
+
+ @Override
+ public void updateUserPassword(final String username, final String password, final CallContext callContext) throws SecurityApiException {
+ userDao.updateUserPassword(username, password, callContext.getUserName());
+ }
+
+ @Override
+ public void updateUserRoles(final String username, final List<String> roles, final CallContext callContext) throws SecurityApiException {
+ userDao.updateUserRoles(username, roles, callContext.getUserName());
+ invalidateJDBCAuthorizationCache(username);
+ }
+
+
+ @Override
+ public void invalidateUser(final String username, final CallContext callContext) throws SecurityApiException {
+ userDao.invalidateUser(username, callContext.getUserName());
+ }
+
+ @Override
+ public List<String> getUserRoles(final String username, final TenantContext tenantContext) {
+ final List<UserRolesModelDao> permissionsModelDao = userDao.getUserRoles(username);
+ return ImmutableList.copyOf(Iterables.transform(permissionsModelDao, new Function<UserRolesModelDao, String>() {
+ @Nullable
+ @Override
+ public String apply(final UserRolesModelDao input) {
+ return input.getRoleName();
+ }
+ }));
+ }
+
+ @Override
+ public void addRoleDefinition(final String role, final List<String> permissions, final CallContext callContext) throws SecurityApiException {
+ final List<String> sanitizedPermissions = sanitizeAndValidatePermissions(permissions);
+ userDao.addRoleDefinition(role, sanitizedPermissions, callContext.getUserName());
+ }
+
+ @Override
+ public List<String> getRoleDefinition(final String role, final TenantContext tenantContext) {
+ final List<RolesPermissionsModelDao> permissionsModelDao = userDao.getRoleDefinition(role);
+ return ImmutableList.copyOf(Iterables.transform(permissionsModelDao, new Function<RolesPermissionsModelDao, String>() {
+ @Nullable
+ @Override
+ public String apply(final RolesPermissionsModelDao input) {
+ return input.getPermission();
+ }
+ }));
+ }
+
+ private List<String> sanitizeAndValidatePermissions(final List<String> permissions) throws SecurityApiException {
+
+ if (permissions == null || permissions.isEmpty()) {
+ throw new SecurityApiException(ErrorCode.SECURITY_INVALID_PERMISSIONS, "null");
+ }
+
+ final HashMap<String, Set<String>> groupToValues = new HashMap<String, Set<String>>();
+ for (final String curPerm : permissions) {
+
+ if (curPerm.equals("*")) {
+ return ImmutableList.of("*");
+ }
+
+ final String[] permissionParts = curPerm.split(":");
+ if (permissionParts.length != 1 && permissionParts.length != 2) {
+ throw new SecurityApiException(ErrorCode.SECURITY_INVALID_PERMISSIONS, curPerm);
+ }
+
+ boolean resolved = false;
+ for (final Permission cur : Permission.values()) {
+ if (resolved) {
+ break;
+ }
+ if (!cur.getGroup().equals(permissionParts[0])) {
+ continue;
+ }
+
+ Set<String> groupPermissions = groupToValues.get(permissionParts[0]);
+ if (groupPermissions == null) {
+ groupPermissions = new HashSet<String>();
+ groupToValues.put(permissionParts[0], groupPermissions);
+ }
+ if (permissionParts.length == 1 || permissionParts[1].equals("*")) {
+ groupPermissions.clear();
+ groupPermissions.add("*");
+ resolved = true;
+ break;
+ }
+
+ if (cur.getValue().equals(permissionParts[1])) {
+ groupPermissions.add(permissionParts[1]);
+ resolved = true;
+ break;
+ }
+ }
+ if (!resolved) {
+ throw new SecurityApiException(ErrorCode.SECURITY_INVALID_PERMISSIONS, curPerm);
+ }
+ }
+
+ final List<String> sanitizedPermissions = new ArrayList<String>();
+ for (String group : groupToValues.keySet()) {
+ final Set<String> groupPermissions = groupToValues.get(group);
+ for (String value : groupPermissions) {
+ sanitizedPermissions.add(String.format("%s:%s", group, value));
+ }
+ }
+ return sanitizedPermissions;
+ }
+
private String[] getAllPermissionsAsStrings() {
if (allPermissions[0] == null) {
synchronized (allPermissions) {
@@ -135,7 +274,22 @@ public class DefaultSecurityApi implements SecurityApi {
}
}
}
-
return allPermissions;
}
+
+ private void invalidateJDBCAuthorizationCache(final String username) {
+ final Collection<Realm> realms = ((DefaultSecurityManager) SecurityUtils.getSecurityManager()).getRealms();
+ final KillBillJdbcRealm killBillJdbcRealm = (KillBillJdbcRealm) Iterables.tryFind(realms, new Predicate<Realm>() {
+ @Override
+ public boolean apply(@Nullable final Realm input) {
+ return (input instanceof KillBillJdbcRealm);
+ }
+ }).orNull();
+
+ if (killBillJdbcRealm != null) {
+ SimplePrincipalCollection principals = new SimplePrincipalCollection();
+ principals.add(username, killBillJdbcRealm.getName());
+ killBillJdbcRealm.clearCachedAuthorizationInfo(principals);
+ }
+ }
}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java
new file mode 100644
index 0000000..92cc4f9
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/DefaultUserDao.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.dao;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.apache.shiro.crypto.RandomNumberGenerator;
+import org.apache.shiro.crypto.SecureRandomNumberGenerator;
+import org.apache.shiro.crypto.hash.SimpleHash;
+import org.apache.shiro.util.ByteSource;
+import org.joda.time.DateTime;
+import org.killbill.billing.ErrorCode;
+import org.killbill.billing.security.SecurityApiException;
+import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher;
+import org.killbill.clock.Clock;
+import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory;
+import org.skife.jdbi.v2.DBI;
+import org.skife.jdbi.v2.Handle;
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.TransactionCallback;
+import org.skife.jdbi.v2.TransactionStatus;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+public class DefaultUserDao implements UserDao {
+
+ private static final RandomNumberGenerator rng = new SecureRandomNumberGenerator();
+ private final IDBI dbi;
+ private final Clock clock;
+
+ @Inject
+ public DefaultUserDao(final IDBI dbi, final Clock clock) {
+ this.dbi = dbi;
+ this.clock = clock;
+ ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(UserModelDao.class));
+ ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(UserRolesModelDao.class));
+ ((DBI) dbi).registerMapper(new LowerToCamelBeanMapperFactory(RolesPermissionsModelDao.class));
+
+ }
+
+ @Override
+ public void insertUser(final String username, final String password, final List<String> roles, final String createdBy) throws SecurityApiException {
+
+ final ByteSource salt = rng.nextBytes();
+ final String hashedPasswordBase64 = new SimpleHash(KillbillCredentialsMatcher.HASH_ALGORITHM_NAME,
+ password, salt.toBase64(), KillbillCredentialsMatcher.HASH_ITERATIONS).toBase64();
+
+ final DateTime createdDate = clock.getUTCNow();
+ dbi.inTransaction(new TransactionCallback<Void>() {
+ @Override
+ public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+ final UserRolesSqlDao userRolesSqlDao = handle.attach(UserRolesSqlDao.class);
+ for (String role : roles) {
+ final RolesPermissionsSqlDao rolesPermissionsSqlDao = handle.attach(RolesPermissionsSqlDao.class);
+ final List<RolesPermissionsModelDao> currentRolePermissions = rolesPermissionsSqlDao.getByRoleName(role);
+ if (currentRolePermissions.isEmpty()) {
+ throw new SecurityApiException(ErrorCode.SECURITY_INVALID_ROLE, role);
+ }
+ userRolesSqlDao.create(new UserRolesModelDao(username, role, createdDate, createdBy));
+ }
+
+ final UsersSqlDao usersSqlDao = handle.attach(UsersSqlDao.class);
+ final UserModelDao userModelDao = usersSqlDao.getByUsername(username);
+ if (userModelDao != null) {
+ throw new SecurityApiException(ErrorCode.SECURITY_USER_ALREADY_EXISTS, username);
+ }
+ usersSqlDao.create(new UserModelDao(username, hashedPasswordBase64, salt.toBase64(), createdDate, createdBy));
+ return null;
+ }
+ });
+ }
+
+ public List<UserRolesModelDao> getUserRoles(final String username) {
+ return dbi.inTransaction(new TransactionCallback<List<UserRolesModelDao>>() {
+ @Override
+ public List<UserRolesModelDao> inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+ final UserRolesSqlDao userRolesSqlDao = handle.attach(UserRolesSqlDao.class);
+ return userRolesSqlDao.getByUsername(username);
+ }
+ });
+
+ }
+
+ @Override
+ public void addRoleDefinition(final String role, final List<String> permissions, final String createdBy) {
+ final DateTime createdDate = clock.getUTCNow();
+ dbi.inTransaction(new TransactionCallback<Void>() {
+ @Override
+ public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+
+ final RolesPermissionsSqlDao rolesPermissionsSqlDao = handle.attach(RolesPermissionsSqlDao.class);
+ final List<RolesPermissionsModelDao> existingRole = rolesPermissionsSqlDao.getByRoleName(role);
+ if (!existingRole.isEmpty()) {
+ throw new SecurityApiException(ErrorCode.SECURITY_ROLE_ALREADY_EXISTS, role);
+ }
+ for (String permission : permissions) {
+ rolesPermissionsSqlDao.create(new RolesPermissionsModelDao(role, permission, createdDate, createdBy));
+ }
+ return null;
+ }
+ });
+
+ }
+
+ @Override
+ public List<RolesPermissionsModelDao> getRoleDefinition(final String role) {
+ return dbi.inTransaction(new TransactionCallback<List<RolesPermissionsModelDao>>() {
+ @Override
+ public List<RolesPermissionsModelDao> inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+ final RolesPermissionsSqlDao rolesPermissionsSqlDao = handle.attach(RolesPermissionsSqlDao.class);
+ return rolesPermissionsSqlDao.getByRoleName(role);
+ }
+ });
+ }
+
+ @Override
+ public void updateUserPassword(final String username, final String password, final String updatedBy) throws SecurityApiException {
+
+ final ByteSource salt = rng.nextBytes();
+ final String hashedPasswordBase64 = new SimpleHash(KillbillCredentialsMatcher.HASH_ALGORITHM_NAME,
+ password, salt.toBase64(), KillbillCredentialsMatcher.HASH_ITERATIONS).toBase64();
+
+ dbi.inTransaction(new TransactionCallback<Void>() {
+ @Override
+ public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+
+ final DateTime updatedDate = clock.getUTCNow();
+ final UsersSqlDao usersSqlDao = handle.attach(UsersSqlDao.class);
+ final UserModelDao userModelDao = usersSqlDao.getByUsername(username);
+ if (userModelDao == null) {
+ throw new SecurityApiException(ErrorCode.SECURITY_INVALID_USER, username);
+ }
+ usersSqlDao.updatePassword(username, hashedPasswordBase64, salt.toBase64(), updatedDate.toDate(), updatedBy);
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public void updateUserRoles(final String username, final List<String> roles, final String updatedBy) throws SecurityApiException {
+ dbi.inTransaction(new TransactionCallback<Void>() {
+ @Override
+ public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+ final DateTime updatedDate = clock.getUTCNow();
+ final UsersSqlDao usersSqlDao = handle.attach(UsersSqlDao.class);
+ final UserModelDao userModelDao = usersSqlDao.getByUsername(username);
+ if (userModelDao == null) {
+ throw new SecurityApiException(ErrorCode.SECURITY_INVALID_USER, username);
+ }
+
+ // Remove stale entries
+ final UserRolesSqlDao userRolesSqlDao = handle.attach(UserRolesSqlDao.class);
+ final List<UserRolesModelDao> existingRoles = userRolesSqlDao.getByUsername(username);
+ for (final UserRolesModelDao curRole : existingRoles) {
+ if (Iterables.tryFind(roles, new Predicate<String>() {
+ @Override
+ public boolean apply(final String input) {
+ return input.equals(curRole.getRoleName());
+ }
+ }).orNull() == null) {
+ userRolesSqlDao.invalidate(username, curRole.getRoleName(), updatedDate.toDate(), updatedBy);
+ }
+ }
+
+ // Add new entries
+ for (final String curNewRole : roles) {
+ if (Iterables.tryFind(existingRoles, new Predicate<UserRolesModelDao>() {
+ @Override
+ public boolean apply(final UserRolesModelDao input) {
+ return input.getRoleName().equals(curNewRole);
+ }
+ }).orNull() == null) {
+ userRolesSqlDao.create(new UserRolesModelDao(username, curNewRole, updatedDate, updatedBy));
+ }
+ }
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public void invalidateUser(final String username, final String updatedBy) throws SecurityApiException {
+ dbi.inTransaction(new TransactionCallback<Void>() {
+ @Override
+ public Void inTransaction(final Handle handle, final TransactionStatus status) throws Exception {
+
+ final DateTime updatedDate = clock.getUTCNow();
+ final UsersSqlDao usersSqlDao = handle.attach(UsersSqlDao.class);
+ final UserModelDao userModelDao = usersSqlDao.getByUsername(username);
+ if (userModelDao == null) {
+ throw new SecurityApiException(ErrorCode.SECURITY_INVALID_USER, username);
+ }
+ usersSqlDao.invalidate(username, updatedDate.toDate(), updatedBy);
+ return null;
+ }
+ });
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsModelDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsModelDao.java
new file mode 100644
index 0000000..bd22436
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsModelDao.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.dao;
+
+import org.joda.time.DateTime;
+
+public class RolesPermissionsModelDao {
+
+ private Long recordId;
+ private String roleName;
+ private String permission;
+ private Boolean isActive;
+ private DateTime createdDate;
+ private String createdBy;
+ private DateTime updatedDate;
+ private String updatedBy;
+
+ public RolesPermissionsModelDao() {
+ }
+
+ public RolesPermissionsModelDao(final Long recordId, final String roleName, final String permission, final Boolean is_active, final DateTime createdDate, final String createdBy, final DateTime updatedDate, final String updatedBy) {
+ this.recordId = recordId;
+ this.roleName = roleName;
+ this.permission = permission;
+ this.isActive = is_active;
+ this.createdDate = createdDate;
+ this.createdBy = createdBy;
+ this.updatedDate = updatedDate;
+ this.updatedBy = updatedBy;
+ }
+
+ public RolesPermissionsModelDao(final String roleName, final String permission, final DateTime createdDate, final String createdBy) {
+ this(-1L, roleName, permission, Boolean.TRUE, createdDate, createdBy, createdDate, createdBy);
+ }
+
+ public Long getRecordId() {
+ return recordId;
+ }
+
+ public void setRecordId(final Long recordId) {
+ this.recordId = recordId;
+ }
+
+ public String getRoleName() {
+ return roleName;
+ }
+
+ public void setRoleName(final String roleName) {
+ this.roleName = roleName;
+ }
+
+ public String getPermission() {
+ return permission;
+ }
+
+ public void setPermission(final String permission) {
+ this.permission = permission;
+ }
+
+ public Boolean getIsActive() {
+ return isActive;
+ }
+
+ public void setIsActive(final Boolean isActive) {
+ this.isActive = isActive;
+ }
+
+ public DateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ public void setCreatedDate(final DateTime createdDate) {
+ this.createdDate = createdDate;
+ }
+
+ public String getCreatedBy() {
+ return createdBy;
+ }
+
+ public void setCreatedBy(final String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public DateTime getUpdatedDate() {
+ return updatedDate;
+ }
+
+ public void setUpdatedDate(final DateTime updatedDate) {
+ this.updatedDate = updatedDate;
+ }
+
+ public String getUpdatedBy() {
+ return updatedBy;
+ }
+
+ public void setUpdatedBy(final String updatedBy) {
+ this.updatedBy = updatedBy;
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java
new file mode 100644
index 0000000..0e25943
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.dao;
+
+import java.util.List;
+
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator;
+
+@UseStringTemplate3StatementLocator
+public interface RolesPermissionsSqlDao extends Transactional<RolesPermissionsSqlDao> {
+
+ @SqlQuery
+ public RolesPermissionsModelDao getByRecordId(@Bind("recordId") final Long recordId);
+
+ @SqlQuery
+ public List<RolesPermissionsModelDao> getByRoleName(@Bind("roleName") final String roleName);
+
+ @SqlUpdate
+ public void create(@SmartBindBean final RolesPermissionsModelDao rolesPermissions);
+
+}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserDao.java
new file mode 100644
index 0000000..bb0ab0d
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserDao.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.dao;
+
+import java.util.List;
+
+import org.killbill.billing.security.SecurityApiException;
+
+public interface UserDao {
+
+ public void insertUser(String username, String password, List<String> roles, String createdBy) throws SecurityApiException;
+
+ public List<UserRolesModelDao> getUserRoles(String username);
+
+ public void addRoleDefinition(String role, List<String> permissions, String createdBy);
+
+ public List<RolesPermissionsModelDao> getRoleDefinition(String role);
+
+ public void updateUserPassword(String username, String password, String createdBy) throws SecurityApiException;
+
+ public void updateUserRoles(String username, List<String> roles, String createdBy) throws SecurityApiException;
+
+ public void invalidateUser(String username, String createdBy) throws SecurityApiException;
+}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserModelDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserModelDao.java
new file mode 100644
index 0000000..88cf250
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserModelDao.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.dao;
+
+import org.joda.time.DateTime;
+
+public class UserModelDao {
+
+ private Long recordId;
+ private String username;
+ private String password;
+ private String passwordSalt;
+ private Boolean isActive;
+ private DateTime createdDate;
+ private String createdBy;
+ private DateTime updatedDate;
+ private String updatedBy;
+
+ public UserModelDao() {
+ }
+
+ public UserModelDao(final Long recordId, final String username, final String password, final String passwordSalt, final Boolean isActive, final DateTime createdDate, final String createdBy, final DateTime updatedDate, final String updatedBy) {
+ this.recordId = recordId;
+ this.username = username;
+ this.password = password;
+ this.passwordSalt = passwordSalt;
+ this.isActive = isActive;
+ this.createdDate = createdDate;
+ this.createdBy = createdBy;
+ this.updatedDate = updatedDate;
+ this.updatedBy = updatedBy;
+ }
+
+ public UserModelDao(final String username, final String password, final String passwordSalt, final DateTime createdDate, final String createdBy) {
+ this(-1L, username, password, passwordSalt, Boolean.TRUE, createdDate, createdBy, createdDate, createdBy);
+ }
+
+ public Long getRecordId() {
+ return recordId;
+ }
+
+ public void setRecordId(final Long recordId) {
+ this.recordId = recordId;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(final String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(final String password) {
+ this.password = password;
+ }
+
+ public DateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ public void setCreatedDate(final DateTime createdDate) {
+ this.createdDate = createdDate;
+ }
+
+ public String getCreatedBy() {
+ return createdBy;
+ }
+
+ public void setCreatedBy(final String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public DateTime getUpdatedDate() {
+ return updatedDate;
+ }
+
+ public void setUpdatedDate(final DateTime updatedDate) {
+ this.updatedDate = updatedDate;
+ }
+
+ public String getUpdatedBy() {
+ return updatedBy;
+ }
+
+ public void setUpdatedBy(final String updatedBy) {
+ this.updatedBy = updatedBy;
+ }
+
+ public String getPasswordSalt() {
+ return passwordSalt;
+ }
+
+ public void setPasswordSalt(final String passwordSalt) {
+ this.passwordSalt = passwordSalt;
+ }
+
+ public Boolean getIsActive() {
+ return isActive;
+ }
+
+ public void setIsActive(final Boolean isActive) {
+ this.isActive = isActive;
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesModelDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesModelDao.java
new file mode 100644
index 0000000..4b12cfc
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesModelDao.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.dao;
+
+import org.joda.time.DateTime;
+
+public class UserRolesModelDao {
+
+ private Long recordId;
+ private String username;
+ private String roleName;
+ private Boolean isActive;
+ private DateTime createdDate;
+ private String createdBy;
+ private DateTime updatedDate;
+ private String updatedBy;
+
+ public UserRolesModelDao() {
+ }
+
+ public UserRolesModelDao(final Long recordId, final String username, final String roleName, final Boolean isActive, final DateTime createdDate, final String createdBy, final DateTime updatedDate, final String updatedBy) {
+ this.recordId = recordId;
+ this.username = username;
+ this.roleName = roleName;
+ this.isActive = isActive;
+ this.createdDate = createdDate;
+ this.createdBy = createdBy;
+ this.updatedDate = updatedDate;
+ this.updatedBy = updatedBy;
+ }
+
+ public UserRolesModelDao(final String username, final String roleName, final DateTime createdDate, final String createdBy) {
+ this(-1L, username, roleName, Boolean.TRUE, createdDate, createdBy, createdDate, createdBy);
+ }
+
+ public Long getRecordId() {
+ return recordId;
+ }
+
+ public void setRecordId(final Long recordId) {
+ this.recordId = recordId;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(final String username) {
+ this.username = username;
+ }
+
+ public String getRoleName() {
+ return roleName;
+ }
+
+ public void setRoleName(final String roleName) {
+ this.roleName = roleName;
+ }
+
+ public Boolean getIsActive() {
+ return isActive;
+ }
+
+ public void setIsActive(final Boolean isActive) {
+ this.isActive = isActive;
+ }
+
+ public DateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ public void setCreatedDate(final DateTime createdDate) {
+ this.createdDate = createdDate;
+ }
+
+ public String getCreatedBy() {
+ return createdBy;
+ }
+
+ public void setCreatedBy(final String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public DateTime getUpdatedDate() {
+ return updatedDate;
+ }
+
+ public void setUpdatedDate(final DateTime updatedDate) {
+ this.updatedDate = updatedDate;
+ }
+
+ public String getUpdatedBy() {
+ return updatedBy;
+ }
+
+ public void setUpdatedBy(final String updatedBy) {
+ this.updatedBy = updatedBy;
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.java
new file mode 100644
index 0000000..c59d80c
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.dao;
+
+import java.util.Date;
+import java.util.List;
+
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator;
+
+@UseStringTemplate3StatementLocator
+public interface UserRolesSqlDao extends Transactional<UserRolesSqlDao> {
+
+ @SqlQuery
+ public UserRolesModelDao getByRecordId(@Bind("recordId") Long recordId);
+
+ @SqlQuery
+ public List<UserRolesModelDao> getByUsername(@Bind("username") String username);
+
+ @SqlUpdate
+ public void create(@SmartBindBean UserRolesModelDao userRolesModelDao);
+
+ @SqlUpdate
+ public void invalidate(@Bind("username") String username,
+ @Bind("roleName") String roleName,
+ @Bind("updatedDate") final Date updatedDate,
+ @Bind("updatedBy") final String updatedBy);
+}
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.java
new file mode 100644
index 0000000..1a7a599
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.dao;
+
+import java.util.Date;
+
+import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator;
+
+@UseStringTemplate3StatementLocator
+public interface UsersSqlDao extends Transactional<UsersSqlDao> {
+
+ @SqlQuery
+ public UserModelDao getByRecordId(@Bind("recordId") Long recordId);
+
+ @SqlQuery
+ public UserModelDao getByUsername(@Bind("username") String username);
+
+ @SqlUpdate
+ public void create(@SmartBindBean UserModelDao userModelDao);
+
+ @SqlUpdate
+ public void updatePassword(@Bind("username") String username,
+ @Bind("password") String password,
+ @Bind("passwordSalt") String passwordSalt,
+ @Bind("updatedDate") Date updatedDate,
+ @Bind("updatedBy") String updatedBy);
+
+ @SqlUpdate
+ public void invalidate(@Bind("username") String username,
+ @Bind("updatedDate") Date updatedDate,
+ @Bind("updatedBy") String updatedBy);
+}
\ No newline at end of file
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJdbcRealm.java b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJdbcRealm.java
new file mode 100644
index 0000000..9806abe
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/realm/KillBillJdbcRealm.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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 javax.inject.Inject;
+import javax.inject.Named;
+import javax.sql.DataSource;
+
+import org.apache.shiro.realm.jdbc.JdbcRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.killbill.billing.platform.glue.KillBillPlatformModuleBase;
+import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher;
+
+public class KillBillJdbcRealm extends JdbcRealm {
+
+ protected static final String KILLBILL_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ? and is_active";
+ protected static final String KILLBILL_USER_ROLES_QUERY = "select role_name from user_roles where username = ? and is_active";
+ protected static final String KILLBILL_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ? and is_active";
+
+ private final DataSource dataSource;
+
+ @Inject
+ public KillBillJdbcRealm(@Named(KillBillPlatformModuleBase.SHIRO_DATA_SOURCE_ID_NAMED) final DataSource dataSource) {
+ super();
+ this.dataSource = dataSource;
+
+ // Tweak JdbcRealm defaults
+ setPermissionsLookupEnabled(true);
+ setAuthenticationQuery(KILLBILL_SALTED_AUTHENTICATION_QUERY);
+ setUserRolesQuery(KILLBILL_USER_ROLES_QUERY);
+ setPermissionsQuery(KILLBILL_PERMISSIONS_QUERY);
+
+ configureSecurity();
+ configureDataSource();
+ }
+
+ @Override
+ public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
+ super.clearCachedAuthorizationInfo(principals);
+ }
+
+ private void configureSecurity() {
+ setSaltStyle(SaltStyle.COLUMN);
+ setCredentialsMatcher(KillbillCredentialsMatcher.getCredentialsMatcher());
+ }
+
+ private void configureDataSource() {
+ setDataSource(dataSource);
+ }
+}
diff --git a/util/src/main/resources/org/killbill/billing/util/ddl.sql b/util/src/main/resources/org/killbill/billing/util/ddl.sql
index 2704dc9..c1482a5 100644
--- a/util/src/main/resources/org/killbill/billing/util/ddl.sql
+++ b/util/src/main/resources/org/killbill/billing/util/ddl.sql
@@ -238,3 +238,49 @@ create table sessions (
, session_data mediumblob default null
, primary key(record_id)
) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+
+
+DROP TABLE IF EXISTS users;
+CREATE TABLE users (
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ username varchar(128) NULL,
+ password varchar(128) NULL,
+ password_salt varchar(128) NULL,
+ is_active bool DEFAULT 1,
+ created_date datetime NOT NULL,
+ created_by varchar(50) NOT NULL,
+ updated_date datetime DEFAULT NULL,
+ updated_by varchar(50) DEFAULT NULL,
+ PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX users_username ON users(username);
+
+
+DROP TABLE IF EXISTS user_roles;
+CREATE TABLE user_roles (
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ username varchar(128) NULL,
+ role_name varchar(128) NULL,
+ is_active bool DEFAULT 1,
+ created_date datetime NOT NULL,
+ created_by varchar(50) NOT NULL,
+ updated_date datetime DEFAULT NULL,
+ updated_by varchar(50) DEFAULT NULL,
+ PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX user_roles_idx ON user_roles(username, role_name);
+
+
+DROP TABLE IF EXISTS roles_permissions;
+CREATE TABLE roles_permissions (
+ record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ role_name varchar(128) NULL,
+ permission varchar(128) NULL,
+ is_active bool DEFAULT 1,
+ created_date datetime NOT NULL,
+ created_by varchar(50) NOT NULL,
+ updated_date datetime DEFAULT NULL,
+ updated_by varchar(50) DEFAULT NULL,
+ PRIMARY KEY(record_id)
+) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
+CREATE INDEX roles_permissions_idx ON roles_permissions(role_name, permission);
diff --git a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.sql.stg
new file mode 100644
index 0000000..5da1774
--- /dev/null
+++ b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/RolesPermissionsSqlDao.sql.stg
@@ -0,0 +1,63 @@
+group RolesPermissionsSqlDao;
+
+
+tableName() ::= "roles_permissions"
+
+tableFields(prefix) ::= <<
+ <prefix>role_name
+, <prefix>permission
+, <prefix>is_active
+, <prefix>created_date
+, <prefix>created_by
+, <prefix>updated_date
+, <prefix>updated_by
+>>
+
+allTableFields(prefix) ::= <<
+ <prefix>record_id
+, <tableFields(prefix)>
+>>
+
+
+tableValues() ::= <<
+ :roleName
+, :permission
+, :isActive
+, :createdDate
+, :createdBy
+, :updatedDate
+, :updatedBy
+>>
+
+
+allTableValues() ::= <<
+ :recordId
+, <tableValues()>
+>>
+
+create() ::= <<
+insert into <tableName()> (
+<tableFields()>
+)
+values (
+<tableValues()>
+)
+;
+>>
+
+getByRecordId() ::= <<
+select <allTableFields()>
+from <tableName()>
+where record_id = :recordId
+and is_active
+;
+>>
+
+
+getByRoleName() ::= <<
+select <allTableFields()>
+from <tableName()>
+where role_name = :roleName
+and is_active
+;
+>>
diff --git a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.sql.stg
new file mode 100644
index 0000000..48b2a92
--- /dev/null
+++ b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UserRolesSqlDao.sql.stg
@@ -0,0 +1,70 @@
+group UserRolesSqlDao;
+
+tableName() ::= "user_roles"
+
+tableFields(prefix) ::= <<
+ <prefix>username
+, <prefix>role_name
+, <prefix>is_active
+, <prefix>created_date
+, <prefix>created_by
+, <prefix>updated_date
+, <prefix>updated_by
+>>
+
+allTableFields(prefix) ::= <<
+ <prefix>record_id
+, <tableFields(prefix)>
+>>
+
+
+tableValues() ::= <<
+ :username
+, :roleName
+, :isActive
+, :createdDate
+, :createdBy
+, :updatedDate
+, :updatedBy
+>>
+
+
+allTableValues() ::= <<
+ :recordId
+, <tableValues()>
+>>
+
+create() ::= <<
+insert into <tableName()> (
+<tableFields()>
+)
+values (
+<tableValues()>
+)
+;
+>>
+
+getByRecordId() ::= <<
+select <allTableFields()>
+from <tableName()>
+where record_id = :recordId
+and is_active
+;
+>>
+
+getByUsername() ::= <<
+select <allTableFields()>
+from <tableName()>
+where username = :username
+and is_active
+;
+>>
+
+invalidate() ::= <<
+update <tableName()>
+set is_active = 0
+where
+username = :username
+and role_name = :roleName
+;
+>>
diff --git a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.sql.stg
new file mode 100644
index 0000000..75a832d
--- /dev/null
+++ b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/UsersSqlDao.sql.stg
@@ -0,0 +1,83 @@
+group UsersSqlDao;
+
+tableName() ::= "users"
+
+tableFields(prefix) ::= <<
+ <prefix>username
+, <prefix>password
+, <prefix>password_salt
+, <prefix>is_active
+, <prefix>created_date
+, <prefix>created_by
+, <prefix>updated_date
+, <prefix>updated_by
+>>
+
+allTableFields(prefix) ::= <<
+ <prefix>record_id
+, <tableFields(prefix)>
+>>
+
+
+tableValues() ::= <<
+ :username
+, :password
+, :passwordSalt
+, :isActive
+, :createdDate
+, :createdBy
+, :updatedDate
+, :updatedBy
+>>
+
+
+allTableValues() ::= <<
+ :recordId
+, <tableValues()>
+>>
+
+create() ::= <<
+insert into <tableName()> (
+<tableFields()>
+)
+values (
+<tableValues()>
+)
+;
+>>
+
+getByRecordId() ::= <<
+select <allTableFields()>
+from <tableName()>
+where record_id = :recordId
+and is_active
+;
+>>
+
+getByUsername() ::= <<
+select <allTableFields()>
+from <tableName()>
+where username = :username
+and is_active
+;
+>>
+
+updatePassword() ::= <<
+update <tableName()>
+set password = :password
+, password_salt = :passwordSalt
+, updated_date = :updatedDate
+, updated_by = :updatedBy
+where username = :username
+and is_active
+;
+>>
+
+invalidate() ::= <<
+update <tableName()>
+set is_active = 0
+where
+username = :username
+;
+>>
+
diff --git a/util/src/test/java/org/killbill/billing/util/glue/TestSecurityModuleNoDB.java b/util/src/test/java/org/killbill/billing/util/glue/TestSecurityModuleNoDB.java
new file mode 100644
index 0000000..a3620db
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/glue/TestSecurityModuleNoDB.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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.glue;
+
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.util.security.shiro.dao.UserDao;
+import org.mockito.Mockito;
+
+public class TestSecurityModuleNoDB extends SecurityModule {
+
+ public TestSecurityModuleNoDB(final KillbillConfigSource configSource) {
+ super(configSource);
+ }
+
+ @Override
+ protected void installDao() {
+ bind(UserDao.class).toInstance(Mockito.mock(UserDao.class));
+ }
+
+}
diff --git a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleNoDB.java b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleNoDB.java
index bb32ac1..7e34658 100644
--- a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleNoDB.java
+++ b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleNoDB.java
@@ -48,8 +48,21 @@ public class TestUtilModuleNoDB extends TestUtilModule {
installAuditMock();
- install(new KillBillShiroModule(configSource));
+ install(new ShiroModuleNoDB(configSource));
install(new KillBillShiroAopModule());
install(new SecurityModule(configSource));
}
+
+ public static class ShiroModuleNoDB extends KillBillShiroModule {
+
+ public ShiroModuleNoDB(final KillbillConfigSource configSource) {
+ super(configSource);
+ }
+
+ protected void configureJDBCRealm() {
+ }
+
+ protected void configureLDAPRealm() {
+ }
+ }
}
diff --git a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleWithEmbeddedDB.java
index 1537ac4..0b56059 100644
--- a/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/util/glue/TestUtilModuleWithEmbeddedDB.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014 Groupon, Inc
- * Copyright 2014 The Billing Project, LLC
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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
@@ -37,8 +37,19 @@ public class TestUtilModuleWithEmbeddedDB extends TestUtilModule {
install(new TagStoreModule(configSource));
install(new CustomFieldModule(configSource));
install(new NonEntityDaoModule(configSource));
+ install(new SecurityModuleWithNoSecurityManager(configSource));
install(new GlobalLockerModule(configSource));
-
bind(TestApiListener.class).asEagerSingleton();
}
+
+ private final class SecurityModuleWithNoSecurityManager extends SecurityModule {
+
+ public SecurityModuleWithNoSecurityManager(final KillbillConfigSource configSource) {
+ super(configSource);
+ }
+
+ protected void installSecurityService() {
+ }
+
+ }
}
diff --git a/util/src/test/java/org/killbill/billing/util/security/api/TestDefaultSecurityApi.java b/util/src/test/java/org/killbill/billing/util/security/api/TestDefaultSecurityApi.java
index 31ef1b2..b83c1fa 100644
--- a/util/src/test/java/org/killbill/billing/util/security/api/TestDefaultSecurityApi.java
+++ b/util/src/test/java/org/killbill/billing/util/security/api/TestDefaultSecurityApi.java
@@ -34,7 +34,7 @@ public class TestDefaultSecurityApi extends UtilTestSuiteNoDB {
configureShiro();
// We don't want the Guice injected one (it has Shiro disabled)
- final SecurityApi securityApi = new DefaultSecurityApi();
+ final SecurityApi securityApi = new DefaultSecurityApi(null);
final Set<Permission> anonsPermissions = securityApi.getCurrentUserPermissions(callContext);
Assert.assertEquals(anonsPermissions.size(), 0);
diff --git a/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java b/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java
new file mode 100644
index 0000000..540f375
--- /dev/null
+++ b/util/src/test/java/org/killbill/billing/util/security/shiro/realm/TestKillBillJdbcRealm.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2014-2015 Groupon, Inc
+ * Copyright 2014-2015 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());
+ 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 testInvalidPermissions() {
+ testInvalidPermissionScenario(null);
+ testInvalidPermissionScenario(ImmutableList.<String>of());
+ 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);
+ }
+ }
+}
diff --git a/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java b/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java
index 60461c9..566518d 100644
--- a/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java
+++ b/util/src/test/java/org/killbill/billing/util/security/TestPermissionAnnotationMethodInterceptor.java
@@ -20,6 +20,8 @@ import javax.inject.Singleton;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthenticatedException;
+import org.killbill.billing.util.glue.TestSecurityModuleNoDB;
+import org.killbill.billing.util.glue.TestUtilModuleNoDB.ShiroModuleNoDB;
import org.mockito.Mockito;
import org.skife.jdbi.v2.IDBI;
import org.testng.Assert;
@@ -74,9 +76,9 @@ public class TestPermissionAnnotationMethodInterceptor extends UtilTestSuiteNoDB
// Shutdown the cache manager to avoid duplicate exceptions
CacheManager.getInstance().shutdown();
final Injector injector = Guice.createInjector(Stage.PRODUCTION,
- new KillBillShiroModule(configSource),
+ new ShiroModuleNoDB(configSource),
new KillBillShiroAopModule(),
- new SecurityModule(configSource),
+ new TestSecurityModuleNoDB(configSource),
new AbstractModule() {
@Override
protected void configure() {
@@ -102,9 +104,9 @@ public class TestPermissionAnnotationMethodInterceptor extends UtilTestSuiteNoDB
// Shutdown the cache manager to avoid duplicate exceptions
CacheManager.getInstance().shutdown();
final Injector injector = Guice.createInjector(Stage.PRODUCTION,
- new KillBillShiroModule(configSource),
+ new ShiroModuleNoDB(configSource),
new KillBillShiroAopModule(),
- new SecurityModule(configSource),
+ new TestSecurityModuleNoDB(configSource),
new AbstractModule() {
@Override
public void configure() {
diff --git a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteNoDB.java b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteNoDB.java
index bb960a0..9bb60b2 100644
--- a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteNoDB.java
+++ b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteNoDB.java
@@ -82,6 +82,8 @@ public class UtilTestSuiteNoDB extends GuicyKillbillTestSuiteNoDB {
@AfterMethod(groups = "fast")
public void afterMethod() throws Exception {
eventBus.stop();
+ // Reset the security manager
+ ThreadContext.unbindSecurityManager();
}
// Security helpers
diff --git a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
index b29dcd3..69abe55 100644
--- a/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/util/UtilTestSuiteWithEmbeddedDB.java
@@ -18,6 +18,8 @@ package org.killbill.billing.util;
import javax.inject.Inject;
+import org.killbill.billing.security.api.SecurityApi;
+import org.killbill.billing.util.security.shiro.dao.UserDao;
import org.skife.jdbi.v2.IDBI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -77,6 +79,9 @@ public abstract class UtilTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
protected IDBI idbi;
@Inject
protected TestApiListener eventsListener;
+ @Inject
+ protected SecurityApi securityApi;
+
@BeforeClass(groups = "slow")
public void beforeClass() throws Exception {