Skip to content

Commit

Permalink
Add support for role namespaces (#94)
Browse files Browse the repository at this point in the history
Added namespaces to roles for grouping and future permission management
  • Loading branch information
billkalter authored Mar 8, 2017
1 parent a8598bf commit 697dc64
Show file tree
Hide file tree
Showing 45 changed files with 1,943 additions and 445 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.bazaarvoice.emodb.auth;

import com.google.inject.BindingAnnotation;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Annotation for the ZooKeeper curator namespaced for the authentication framework.
*/
@BindingAnnotation
@Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
public @interface AuthZooKeeper {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import com.bazaarvoice.emodb.auth.apikey.ApiKey;
import com.bazaarvoice.emodb.auth.apikey.ApiKeyRealm;
import com.bazaarvoice.emodb.auth.apikey.ApiKeySecurityManager;
import com.bazaarvoice.emodb.auth.identity.AuthIdentityManager;
import com.bazaarvoice.emodb.auth.permissions.PermissionManager;
import com.bazaarvoice.emodb.auth.identity.AuthIdentityReader;
import com.bazaarvoice.emodb.auth.permissions.PermissionReader;
import com.bazaarvoice.emodb.auth.shiro.GuavaCacheManager;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.mgt.SecurityManager;
Expand All @@ -22,8 +22,8 @@ public class SecurityManagerBuilder {
private final static String DEFAULT_REALM_NAME = "DefaultRealm";

protected String _realmName = DEFAULT_REALM_NAME;
protected AuthIdentityManager<ApiKey> _authIdentityManager;
protected PermissionManager _permissionManager;
protected AuthIdentityReader<ApiKey> _authIdentityReader;
protected PermissionReader _permissionReader;
protected String _anonymousId;
protected CacheManager _cacheManager;

Expand All @@ -43,13 +43,13 @@ public SecurityManagerBuilder withRealmName(String realmName) {
return this;
}

public SecurityManagerBuilder withAuthIdentityManager(AuthIdentityManager<ApiKey> authIdentityManager) {
_authIdentityManager = checkNotNull(authIdentityManager, "authIdentityManager");
public SecurityManagerBuilder withAuthIdentityReader(AuthIdentityReader<ApiKey> authIdentityReader) {
_authIdentityReader = checkNotNull(authIdentityReader, "authIdentityReader");
return this;
}

public SecurityManagerBuilder withPermissionManager(PermissionManager permissionManager) {
_permissionManager = checkNotNull(permissionManager, "permissionManager");
public SecurityManagerBuilder withPermissionReader(PermissionReader permissionReader) {
_permissionReader = checkNotNull(permissionReader, "permissionReader");
return this;
}

Expand All @@ -69,12 +69,12 @@ public SecurityManagerBuilder withAnonymousAccessAs(@Nullable String id) {
}

public EmoSecurityManager build() {
checkNotNull(_authIdentityManager, "authIdentityManager not set");
checkNotNull(_permissionManager, "permissionManager not set");
checkNotNull(_authIdentityReader, "authIdentityManager not set");
checkNotNull(_permissionReader, "permissionManager not set");
if(_cacheManager == null) { // intended for test use
_cacheManager = new GuavaCacheManager(null);
}
ApiKeyRealm realm = new ApiKeyRealm(_realmName, _cacheManager, _authIdentityManager, _permissionManager, _anonymousId);
ApiKeyRealm realm = new ApiKeyRealm(_realmName, _cacheManager, _authIdentityReader, _permissionReader, _anonymousId);
LifecycleUtils.init(realm);

return new ApiKeySecurityManager(realm);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.bazaarvoice.emodb.auth.apikey;

import com.bazaarvoice.emodb.auth.identity.AuthIdentityManager;
import com.bazaarvoice.emodb.auth.permissions.PermissionManager;
import com.bazaarvoice.emodb.auth.identity.AuthIdentityReader;
import com.bazaarvoice.emodb.auth.permissions.PermissionIDs;
import com.bazaarvoice.emodb.auth.permissions.PermissionReader;
import com.bazaarvoice.emodb.auth.shiro.AnonymousCredentialsMatcher;
import com.bazaarvoice.emodb.auth.shiro.AnonymousToken;
import com.bazaarvoice.emodb.auth.shiro.InvalidatableCacheManager;
Expand Down Expand Up @@ -43,8 +44,8 @@ public class ApiKeyRealm extends AuthorizingRealm {

private final Logger _log = LoggerFactory.getLogger(getClass());

private final AuthIdentityManager<ApiKey> _authIdentityManager;
private final PermissionManager _permissionManager;
private final AuthIdentityReader<ApiKey> _authIdentityReader;
private final PermissionReader _permissionReader;
private final String _anonymousId;
private final boolean _clearCaches;

Expand All @@ -62,18 +63,18 @@ public class ApiKeyRealm extends AuthorizingRealm {
private String _rolesCacheName;
private String _internalAuthorizationCacheName;

public ApiKeyRealm(String name, CacheManager cacheManager, AuthIdentityManager<ApiKey> authIdentityManager,
PermissionManager permissionManager, @Nullable String anonymousId) {
public ApiKeyRealm(String name, CacheManager cacheManager, AuthIdentityReader<ApiKey> authIdentityReader,
PermissionReader permissionReader, @Nullable String anonymousId) {
super(null, AnonymousCredentialsMatcher.anonymousOrMatchUsing(new SimpleCredentialsMatcher()));


_authIdentityManager = checkNotNull(authIdentityManager, "authIdentityManager");
_permissionManager = checkNotNull(permissionManager, "permissionManager");
_authIdentityReader = checkNotNull(authIdentityReader, "authIdentityReader");
_permissionReader = checkNotNull(permissionReader, "permissionReader");
_anonymousId = anonymousId;

setName(checkNotNull(name, "name"));
setAuthenticationTokenClass(ApiKeyAuthenticationToken.class);
setPermissionResolver(permissionManager.getPermissionResolver());
setPermissionResolver(permissionReader.getPermissionResolver());
setRolePermissionResolver(createRolePermissionResolver());
setCacheManager(prepareCacheManager(cacheManager));
setAuthenticationCachingEnabled(true);
Expand Down Expand Up @@ -154,7 +155,7 @@ public boolean isCurrentValue(String key, AuthorizationInfo value) {
@Override
public boolean isCurrentValue(String key, RolePermissionSet value) {
// The key is the role name
Set<Permission> currentPermissions = _permissionManager.getAllForRole(key);
Set<Permission> currentPermissions = _permissionReader.getPermissions(PermissionIDs.forRole(key));
return value.permissions().equals(currentPermissions);
}
};
Expand Down Expand Up @@ -210,7 +211,7 @@ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
* Gets the authentication info for an API key from the source (not from cache).
*/
private AuthenticationInfo getUncachedAuthenticationInfoForKey(String id) {
ApiKey apiKey = _authIdentityManager.getIdentity(id);
ApiKey apiKey = _authIdentityReader.getIdentity(id);
if (apiKey == null) {
return null;
}
Expand Down Expand Up @@ -319,28 +320,28 @@ private RolePermissionResolver createRolePermissionResolver() {
return new RolePermissionResolver () {
@Override
public Collection<Permission> resolvePermissionsInRole(String role) {
return getPermissions(role);
return getRolePermissions(role);
}
};
}

/**
* Gets the permissions for a role. If possible the permissions are cached for efficiency.
*/
protected Collection<Permission> getPermissions(String role) {
protected Collection<Permission> getRolePermissions(String role) {
if (role == null) {
return null;
}
Cache<String, RolePermissionSet> cache = getAvailableRolesCache();

if (cache == null) {
return _permissionManager.getAllForRole(role);
return _permissionReader.getPermissions(PermissionIDs.forRole(role));
}

RolePermissionSet rolePermissionSet = cache.get(role);

if (rolePermissionSet == null) {
Set<Permission> permissions = _permissionManager.getAllForRole(role);
Set<Permission> permissions = _permissionReader.getPermissions(PermissionIDs.forRole(role));
rolePermissionSet = new SimpleRolePermissionSet(permissions);
cache.put(role, rolePermissionSet);
}
Expand Down Expand Up @@ -417,7 +418,7 @@ private void cacheAuthorizationInfoByInternalId(String internalId, Authorization
*/
private AuthorizationInfo getUncachedAuthorizationInfoByInternalId(String internalId) {
// Retrieve the roles by internal ID
Set<String> roles = _authIdentityManager.getRolesByInternalId(internalId);
Set<String> roles = _authIdentityReader.getRolesByInternalId(internalId);
if (roles == null) {
_log.debug("Authorization info requested for non-existent internal id {}", internalId);
return _nullAuthorizationInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
*
* SecurityManager securityManager = SecurityManagerBuilder.create()
* .withRealmName("MyRealm")
* .withAuthIdentityManager(identityManager)
* .withPermissionManager(permissionManager)
* .withAuthIdentityReader(identityManager)
* .withPermissionReader(permissionManager)
* .build();
*
* new DropWizardAuthConfigurator(securityManager).configure(environment);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
package com.bazaarvoice.emodb.auth.identity;

import java.util.Set;

/**
* Manager for identities.
*/
public interface AuthIdentityManager<T extends AuthIdentity> {

/**
* Gets an entity by ID. Returns the identity, or null if no such identity exists.
*/
T getIdentity(String id);
public interface AuthIdentityManager<T extends AuthIdentity> extends AuthIdentityReader<T> {

/**
* Updates an identity, or creates one if no matching identity already exists.
Expand All @@ -21,9 +14,4 @@ public interface AuthIdentityManager<T extends AuthIdentity> {
* Deletes an identity.
*/
void deleteIdentity(String id);

/**
* Gets the roles associated with an identity by its internal ID.
*/
Set<String> getRolesByInternalId(String internalId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.bazaarvoice.emodb.auth.identity;

import org.apache.shiro.authz.AuthorizationInfo;

import java.util.Set;

/**
* Minimal interface for read-only access to authentication identities.
*/
public interface AuthIdentityReader<T extends AuthIdentity> {

/**
* Gets an entity by ID, such as its API key. Returns the identity, or null if no such identity exists.
*/
T getIdentity(String id);

/**
* Gets the roles associated with an identity by its internal ID.
*
* Although role management is done using {@link com.bazaarvoice.emodb.auth.role.RoleIdentifier} Shiro's
* authorization framework represents roles as Strings, as demonstrated by {@link AuthorizationInfo#getRoles())}.
* Since this reader is used as part of the authorization framework it provides an interface compatible with what
* Shiro requires.
*/
Set<String> getRolesByInternalId(String internalId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,52 +16,44 @@
*/
public class InMemoryPermissionManager implements PermissionManager {
private final PermissionResolver _permissionResolver;
private final SetMultimap<String, Permission> _principalPermissionMap = HashMultimap.create();
private final SetMultimap<String, Permission> _rolePermissionMap = HashMultimap.create();
private final SetMultimap<String, Permission> _permissionMap = HashMultimap.create();

public InMemoryPermissionManager(PermissionResolver permissionResolver) {
_permissionResolver = permissionResolver;
}

@Override
public Set<Permission> getAllForRole(String role) {
return getAll(_rolePermissionMap, role);
public Set<Permission> getPermissions(String id) {
checkNotNull(id, "id");
return _permissionMap.get(id);
}

@Override
public void updateForRole(String role, PermissionUpdateRequest updates) {
update(_rolePermissionMap, role, updates);
public void updatePermissions(String id, PermissionUpdateRequest updates) {
if (updates.isRevokeRest()) {
revokePermissions(id);
}
for (String permissionString : updates.getPermitted()) {
Permission permission = _permissionResolver.resolvePermission(permissionString);
_permissionMap.put(id, permission);
}
if (!updates.isRevokeRest()) {
for (String permissionString : updates.getRevoked()) {
Permission permission = _permissionResolver.resolvePermission(permissionString);
_permissionMap.remove(id, permission);
}
}
}

@Override
public void revokeAllForRole(String role) {
revokeAll(_rolePermissionMap, role);
public void revokePermissions(String id) {
checkNotNull(id, "id");
_permissionMap.removeAll(id);
}

@Override
public Iterable<Map.Entry<String, Set<Permission>>> getAll() {
return Multimaps.asMap(_rolePermissionMap).entrySet();
}

private Set<Permission> getAll(SetMultimap<String, Permission> permissionMap, String key) {
checkNotNull(key, "key");
return permissionMap.get(key);
}

private void update(SetMultimap<String, Permission> permissionMap, String key, PermissionUpdateRequest request) {
for (String permissionString : request.getPermitted()) {
Permission permission = _permissionResolver.resolvePermission(permissionString);
permissionMap.put(key, permission);
}
for (String permissionString : request.getRevoked()) {
Permission permission = _permissionResolver.resolvePermission(permissionString);
permissionMap.remove(key, permission);
}
}

private void revokeAll(SetMultimap<String, Permission> permissionMap, String key) {
checkNotNull(key, "key");
permissionMap.removeAll(key);
return Multimaps.asMap(_permissionMap).entrySet();
}

@Override
Expand All @@ -70,7 +62,6 @@ public PermissionResolver getPermissionResolver() {
}

public void reset() {
_principalPermissionMap.clear();
_rolePermissionMap.clear();
_permissionMap.clear();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.bazaarvoice.emodb.auth.permissions;

import com.bazaarvoice.emodb.auth.role.RoleIdentifier;

/**
* Static helper class for converting type-specific permissions to flat namespaced Strings used by
* {@link PermissionReader#getPermissions(String)}. While currently only roles can have permissions attached to them
* this convention allows for future additional permission types without need for a separate permission management
* system.
*/
public final class PermissionIDs {

public static String forRole(RoleIdentifier id) {
return forRole(id.toString());
}

public static String forRole(String role) {
return "role:" + role;
}
}
Loading

0 comments on commit 697dc64

Please sign in to comment.