/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.usergrid.security.shiro;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.usergrid.persistence.cache.CacheFactory;
import org.apache.usergrid.persistence.cache.CacheScope;
import org.apache.usergrid.persistence.cache.ScopedCache;
import org.apache.usergrid.persistence.model.entity.SimpleId;
import org.apache.usergrid.security.shiro.principals.*;
import org.apache.usergrid.security.shiro.utils.LocalShiroCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
/**
* Plugin Usergrid cache for Shiro.
*/
public class ShiroCache<K, V> implements Cache<K,V> {
private static final Logger logger = LoggerFactory.getLogger( ShiroCache.class );
private final CacheFactory<String, V> cacheFactory;
private final TypeReference typeRef;
private final Integer cacheTtl;
private final LocalShiroCache localShiroCache;
public ShiroCache(TypeReference typeRef, CacheFactory<String, V> cacheFactory, Integer cacheTtl, LocalShiroCache<K,V> localShiroCache) {
this.typeRef = typeRef;
this.cacheFactory = cacheFactory;
this.cacheTtl = cacheTtl;
this.localShiroCache = localShiroCache;
}
@Override
public V get(K key) throws CacheException {
if ( cacheTtl == 0 ) return null;
V value;
//check cache first
value = (V) localShiroCache.get(getKeyString(key));
if( value !=null ){
if(logger.isTraceEnabled()) {
logger.trace("Shiro value served from local cache: {}", value);
}
return value;
}
ScopedCache<String, V> scopedCache = getCacheScope(key);
if ( scopedCache != null ) {
value = scopedCache.get(getKeyString(key), typeRef);
if(value != null) {
if(logger.isTraceEnabled()) {
logger.trace("Shiro value service from cassandra cache: {}", value);
}
localShiroCache.put(getKeyString(key), value);
}
if ( logger.isTraceEnabled() ) {
if (value instanceof UsergridAuthorizationInfo) {
UsergridAuthorizationInfo info = (UsergridAuthorizationInfo) value;
logger.trace("Got from AUTHZ cache {} for app {}", getKeyString(key), info.toString());
} else if (value instanceof UsergridAuthenticationInfo) {
UsergridAuthenticationInfo info = (UsergridAuthenticationInfo) value;
logger.trace("Got from AUTHC cache {} for app {}", getKeyString(key), info.toString());
} else if (value == null) {
logger.trace("Got NULL from cache app {} for key {}", getKeyString(key), key.toString());
}
}
}
return value;
}
@Override
public V put(K key, V value) throws CacheException {
if ( cacheTtl == 0 ) return null;
ScopedCache<String, V> scopedCache = getCacheScope(key);
if ( scopedCache != null ) {
V ret = scopedCache.put(getKeyString(key), value, cacheTtl);
localShiroCache.invalidate(getKeyString(key));
if ( logger.isTraceEnabled() ) {
if (value instanceof UsergridAuthorizationInfo) {
UsergridAuthorizationInfo info = (UsergridAuthorizationInfo) value;
logger.trace("Put to AUTHZ cache {} for app {}", getKeyString(key), info.toString());
} else if (value instanceof UsergridAuthenticationInfo) {
UsergridAuthenticationInfo info = (UsergridAuthenticationInfo) value;
logger.trace("Put to AUTHC cache {} for app {}", getKeyString(key), info.toString());
}
}
return ret;
}
return null;
}
@Override
public V remove(K key) throws CacheException {
if ( cacheTtl == 0 ) return null;
ScopedCache<String, V> scopedCache = getCacheScope(key);
if ( scopedCache != null ) {
scopedCache.remove( getKeyString(key) );
}
localShiroCache.invalidate(getKeyString(key));
return null;
}
@Override
public void clear() throws CacheException {
localShiroCache.invalidateAll();
// no-op: Usergrid logic will invalidate cache as necessary
}
@Override
public int size() {
return 0; // TODO?
}
@Override
public Set<K> keys() {
return Collections.EMPTY_SET;
}
@Override
public Collection<V> values() {
return Collections.EMPTY_LIST;
}
/** get cache for application scope */
private ScopedCache<String, V> getCacheScope( K key ) {
PrincipalIdentifier principal;
if ( key instanceof SimplePrincipalCollection) {
SimplePrincipalCollection spc = (SimplePrincipalCollection) key;
principal = (PrincipalIdentifier) spc.getPrimaryPrincipal();
} else {
principal = (PrincipalIdentifier)key;
}
CacheScope scope = new CacheScope(new SimpleId(principal.getApplicationId(), "application"));
ScopedCache<String, V> scopedCache = cacheFactory.getScopedCache(scope);
return scopedCache;
}
/** key is the user UUID in string form + class name of key */
private String getKeyString( K key ) {
String ret = null;
Throwable throwable = null;
String errorMessage = null;
try {
final String typeName = typeRef.getType().getTypeName();
if (key instanceof SimplePrincipalCollection) {
SimplePrincipalCollection spc = (SimplePrincipalCollection) key;
if (spc.getPrimaryPrincipal() instanceof UserPrincipal) {
// principal is a user, use UUID as cache key
UserPrincipal p = (UserPrincipal) spc.getPrimaryPrincipal();
ret = p.getUser().getUuid().toString() + "_" + typeName;
} else if (spc.getPrimaryPrincipal() instanceof PrincipalIdentifier) {
// principal is not user, try to get something unique as cache key
PrincipalIdentifier p = (PrincipalIdentifier) spc.getPrimaryPrincipal();
if (p.getAccessTokenCredentials() != null) {
ret = p.getAccessTokenCredentials().getToken() + "_" + typeName;
} else {
if (p instanceof OrganizationPrincipal){
OrganizationPrincipal op = (OrganizationPrincipal) p;
ret = op.getOrganizationId() + "_" + typeName;
}else{
ret = p.getApplicationId() + "_" + typeName;
}
}
} else {
errorMessage = "Unknown principal type: " + key.getClass().getSimpleName();
}
} else if (key instanceof ApplicationGuestPrincipal) {
ApplicationGuestPrincipal agp = (ApplicationGuestPrincipal) key;
ret = agp.getApplicationId() + "_" + typeName;
} else if (key instanceof ApplicationPrincipal) {
ApplicationPrincipal ap = (ApplicationPrincipal) key;
ret = ap.getApplicationId() + "_" + typeName;
} else if (key instanceof OrganizationPrincipal) {
OrganizationPrincipal op = (OrganizationPrincipal) key;
ret = op.getOrganizationId() + "_" + typeName;
} else if (key instanceof UserPrincipal) {
UserPrincipal up = (UserPrincipal) key;
ret = up.getUser().getUuid() + "_" + typeName;
} else {
errorMessage = "Unknown key type: " + key.getClass().getSimpleName();
}
} catch ( Throwable t ) {
throwable = t;
}
if ( throwable != null ) {
errorMessage = "Error generating cache key for key type " + key.getClass().getSimpleName();
throw new CacheException( errorMessage, throwable );
}
if ( ret == null ) {
throw new CacheException( errorMessage );
}
return ret;
}
}