| /* |
| * 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.tokens.cassandra; |
| |
| |
| import java.nio.ByteBuffer; |
| import java.util.*; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.springframework.beans.factory.annotation.Autowired; |
| import org.springframework.beans.factory.annotation.Qualifier; |
| import org.springframework.util.Assert; |
| import org.apache.usergrid.persistence.EntityManagerFactory; |
| import org.apache.usergrid.persistence.cassandra.CassandraService; |
| import org.apache.usergrid.persistence.entities.Application; |
| import org.apache.usergrid.security.AuthPrincipalInfo; |
| import org.apache.usergrid.security.AuthPrincipalType; |
| import org.apache.usergrid.security.tokens.TokenCategory; |
| import org.apache.usergrid.security.tokens.TokenInfo; |
| import org.apache.usergrid.security.tokens.TokenService; |
| import org.apache.usergrid.security.tokens.exceptions.BadTokenException; |
| import org.apache.usergrid.security.tokens.exceptions.ExpiredTokenException; |
| import org.apache.usergrid.security.tokens.exceptions.InvalidTokenException; |
| import org.apache.usergrid.utils.JsonUtils; |
| import org.apache.usergrid.utils.UUIDUtils; |
| |
| import me.prettyprint.hector.api.Keyspace; |
| import me.prettyprint.hector.api.beans.HColumn; |
| import me.prettyprint.hector.api.mutation.Mutator; |
| |
| import static java.lang.System.currentTimeMillis; |
| |
| import static me.prettyprint.hector.api.factory.HFactory.createColumn; |
| import static me.prettyprint.hector.api.factory.HFactory.createMutator; |
| import static org.apache.commons.codec.binary.Base64.decodeBase64; |
| import static org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString; |
| import static org.apache.commons.codec.digest.DigestUtils.sha; |
| import static org.apache.usergrid.persistence.cassandra.CassandraPersistenceUtils.getColumnMap; |
| import static org.apache.usergrid.persistence.cassandra.CassandraService.PRINCIPAL_TOKEN_CF; |
| import static org.apache.usergrid.persistence.cassandra.CassandraService.TOKENS_CF; |
| import static org.apache.usergrid.security.tokens.TokenCategory.ACCESS; |
| import static org.apache.usergrid.security.tokens.TokenCategory.EMAIL; |
| import static org.apache.usergrid.security.tokens.TokenCategory.OFFLINE; |
| import static org.apache.usergrid.security.tokens.TokenCategory.REFRESH; |
| import static org.apache.usergrid.utils.ConversionUtils.HOLDER; |
| import static org.apache.usergrid.utils.ConversionUtils.bytebuffer; |
| import static org.apache.usergrid.utils.ConversionUtils.bytes; |
| import static org.apache.usergrid.utils.ConversionUtils.getLong; |
| import static org.apache.usergrid.utils.ConversionUtils.string; |
| import static org.apache.usergrid.utils.ConversionUtils.uuid; |
| import static org.apache.usergrid.utils.MapUtils.hasKeys; |
| import static org.apache.usergrid.utils.MapUtils.hashMap; |
| import static org.apache.usergrid.utils.UUIDUtils.getTimestampInMillis; |
| import static org.apache.usergrid.persistence.cassandra.Serializers.*; |
| |
| |
| public class TokenServiceImpl implements TokenService { |
| |
| private static final Logger logger = LoggerFactory.getLogger( TokenServiceImpl.class ); |
| |
| public static final String PROPERTIES_AUTH_TOKEN_SECRET_SALT = "usergrid.auth.token_secret_salt"; |
| public static final String PROPERTIES_AUTH_TOKEN_EXPIRES_FROM_LAST_USE = |
| "usergrid.auth.token_expires_from_last_use"; |
| public static final String PROPERTIES_AUTH_TOKEN_REFRESH_REUSES_ID = "usergrid.auth.token_refresh_reuses_id"; |
| |
| private static final String TOKEN_UUID = "uuid"; |
| private static final String TOKEN_TYPE = "type"; |
| private static final String TOKEN_CREATED = "created"; |
| private static final String TOKEN_ACCESSED = "accessed"; |
| private static final String TOKEN_INACTIVE = "inactive"; |
| private static final String TOKEN_DURATION = "duration"; |
| private static final String TOKEN_PRINCIPAL_TYPE = "principal"; |
| private static final String TOKEN_ENTITY = "entity"; |
| private static final String TOKEN_APPLICATION = "application"; |
| private static final String TOKEN_STATE = "state"; |
| |
| private static final String TOKEN_TYPE_ACCESS = "access"; |
| |
| |
| private static final Set<String> TOKEN_PROPERTIES; |
| |
| |
| static { |
| HashSet<String> set = new HashSet<String>(); |
| set.add( TOKEN_UUID ); |
| set.add( TOKEN_TYPE ); |
| set.add( TOKEN_CREATED ); |
| set.add( TOKEN_ACCESSED ); |
| set.add( TOKEN_INACTIVE ); |
| set.add( TOKEN_PRINCIPAL_TYPE ); |
| set.add( TOKEN_ENTITY ); |
| set.add( TOKEN_APPLICATION ); |
| set.add( TOKEN_STATE ); |
| set.add( TOKEN_DURATION ); |
| TOKEN_PROPERTIES = Collections.unmodifiableSet(set); |
| } |
| |
| |
| private static final HashSet<String> REQUIRED_TOKEN_PROPERTIES = new HashSet<String>(); |
| |
| |
| static { |
| REQUIRED_TOKEN_PROPERTIES.add( TOKEN_UUID ); |
| REQUIRED_TOKEN_PROPERTIES.add( TOKEN_TYPE ); |
| REQUIRED_TOKEN_PROPERTIES.add( TOKEN_CREATED ); |
| REQUIRED_TOKEN_PROPERTIES.add( TOKEN_ACCESSED ); |
| REQUIRED_TOKEN_PROPERTIES.add( TOKEN_INACTIVE ); |
| REQUIRED_TOKEN_PROPERTIES.add( TOKEN_DURATION ); |
| } |
| |
| |
| public static final String TOKEN_SECRET_SALT = "super secret token value"; |
| |
| // Short-lived token is good for 24 hours |
| public static final long SHORT_TOKEN_AGE = 24 * 60 * 60 * 1000; |
| |
| // Long-lived token is good for 7 days |
| public static final long LONG_TOKEN_AGE = 7 * 24 * 60 * 60 * 1000; |
| |
| String tokenSecretSalt = TOKEN_SECRET_SALT; |
| |
| long maxPersistenceTokenAge = LONG_TOKEN_AGE; |
| |
| Map<TokenCategory, Long> tokenExpirations = |
| hashMap( ACCESS, LONG_TOKEN_AGE ).map( REFRESH, LONG_TOKEN_AGE ).map( EMAIL, LONG_TOKEN_AGE ) |
| .map( OFFLINE, LONG_TOKEN_AGE ); |
| |
| long maxAccessTokenAge = SHORT_TOKEN_AGE; |
| long maxRefreshTokenAge = LONG_TOKEN_AGE; |
| long maxEmailTokenAge = LONG_TOKEN_AGE; |
| long maxOfflineTokenAge = LONG_TOKEN_AGE; |
| |
| protected CassandraService cassandra; |
| |
| protected Properties properties; |
| |
| protected EntityManagerFactory emf; |
| |
| |
| public TokenServiceImpl() { |
| |
| } |
| |
| |
| long getExpirationProperty( String name, long default_expiration ) { |
| long expires = Long.parseLong( |
| properties.getProperty( "usergrid.auth.token." + name + ".expires", "" + default_expiration ) ); |
| return expires > 0 ? expires : default_expiration; |
| } |
| |
| |
| long getExpirationForTokenType( TokenCategory tokenCategory ) { |
| Long l = tokenExpirations.get( tokenCategory ); |
| if ( l != null ) { |
| return l; |
| } |
| return SHORT_TOKEN_AGE; |
| } |
| |
| |
| void setExpirationFromProperties( String name ) { |
| TokenCategory tokenCategory = TokenCategory.valueOf( name.toUpperCase() ); |
| long expires = Long.parseLong( properties.getProperty( "usergrid.auth.token." + name + ".expires", |
| "" + getExpirationForTokenType( tokenCategory ) ) ); |
| if ( expires > 0 ) { |
| tokenExpirations.put( tokenCategory, expires ); |
| } |
| logger.info( "{} token expires after {} seconds", name, getExpirationForTokenType( tokenCategory ) / 1000 ); |
| } |
| |
| |
| @Autowired |
| public void setProperties( Properties properties ) { |
| this.properties = properties; |
| |
| if ( properties != null ) { |
| maxPersistenceTokenAge = getExpirationProperty( "persistence", maxPersistenceTokenAge ); |
| |
| setExpirationFromProperties( "access" ); |
| setExpirationFromProperties( "refresh" ); |
| setExpirationFromProperties( "email" ); |
| setExpirationFromProperties( "offline" ); |
| |
| tokenSecretSalt = properties.getProperty( PROPERTIES_AUTH_TOKEN_SECRET_SALT, TOKEN_SECRET_SALT ); |
| } |
| } |
| |
| |
| @Override |
| public String createToken( TokenCategory tokenCategory, String type, AuthPrincipalInfo principal, |
| Map<String, Object> state, long duration ) throws Exception { |
| return createToken( tokenCategory, type, principal, state, duration, System.currentTimeMillis() ); |
| } |
| |
| |
| /** Exposed for testing purposes. The interface does not allow creation timestamp checking */ |
| public String createToken( TokenCategory tokenCategory, String type, AuthPrincipalInfo principal, |
| Map<String, Object> state, long duration, long creationTimestamp ) throws Exception { |
| |
| long maxTokenTtl = getMaxTtl( tokenCategory, principal ); |
| |
| if ( duration > maxTokenTtl ) { |
| throw new IllegalArgumentException( |
| String.format( "Your token age cannot be more than the maximum age of %d milliseconds", |
| maxTokenTtl ) ); |
| } |
| |
| if ( duration == 0 ) { |
| duration = maxTokenTtl; |
| } |
| |
| if ( principal != null ) { |
| Assert.notNull( principal.getType() ); |
| Assert.notNull( principal.getApplicationId() ); |
| Assert.notNull( principal.getUuid() ); |
| } |
| |
| UUID uuid = UUIDUtils.newTimeUUID( creationTimestamp ); |
| long timestamp = getTimestampInMillis( uuid ); |
| if ( type == null ) { |
| type = TOKEN_TYPE_ACCESS; |
| } |
| TokenInfo tokenInfo = new TokenInfo( uuid, type, timestamp, timestamp, 0, duration, principal, state ); |
| putTokenInfo( tokenInfo ); |
| return getTokenForUUID( tokenInfo, tokenCategory, uuid ); |
| } |
| |
| |
| @Override |
| public TokenInfo getTokenInfo( String token ) throws Exception { |
| |
| UUID uuid = getUUIDForToken( token ); |
| |
| if ( uuid == null ) { |
| return null; |
| } |
| |
| TokenInfo tokenInfo = getTokenInfo( uuid ); |
| |
| if ( tokenInfo == null ) { |
| return null; |
| } |
| |
| //update the token |
| long now = currentTimeMillis(); |
| |
| long maxTokenTtl = getMaxTtl( TokenCategory.getFromBase64String( token ), tokenInfo.getPrincipal() ); |
| |
| Mutator<UUID> batch = createMutator( cassandra.getSystemKeyspace(), ue ); |
| |
| HColumn<String, Long> col = |
| createColumn( TOKEN_ACCESSED, now, calcTokenTime( tokenInfo.getExpiration( maxTokenTtl ) ), |
| se, le ); |
| batch.addInsertion( uuid, TOKENS_CF, col ); |
| |
| long inactive = now - tokenInfo.getAccessed(); |
| if ( inactive > tokenInfo.getInactive() ) { |
| col = createColumn( TOKEN_INACTIVE, inactive, calcTokenTime( tokenInfo.getExpiration( maxTokenTtl ) ), |
| se, le ); |
| batch.addInsertion( uuid, TOKENS_CF, col ); |
| tokenInfo.setInactive( inactive ); |
| } |
| |
| batch.execute(); |
| |
| return tokenInfo; |
| } |
| |
| |
| /** Get the max ttl per app. This is null safe,and will return the default in the case of missing data */ |
| private long getMaxTtl( TokenCategory tokenCategory, AuthPrincipalInfo principal ) throws Exception { |
| |
| if ( principal == null ) { |
| return maxPersistenceTokenAge; |
| } |
| long defaultMaxTtlForTokenType = getExpirationForTokenType( tokenCategory ); |
| |
| Application application = emf.getEntityManager( principal.getApplicationId() ) |
| .get( principal.getApplicationId(), Application.class ); |
| |
| if ( application == null ) { |
| return defaultMaxTtlForTokenType; |
| } |
| |
| // set the max to the default |
| long maxTokenTtl = defaultMaxTtlForTokenType; |
| |
| // it's been defined on the expiration, override it |
| if ( application.getAccesstokenttl() != null ) { |
| maxTokenTtl = application.getAccesstokenttl(); |
| |
| // it's set to 0 which equals infinity, set our expiration to |
| // LONG.MAX |
| if ( maxTokenTtl == 0 ) { |
| maxTokenTtl = Long.MAX_VALUE; |
| } |
| } |
| |
| return maxTokenTtl; |
| } |
| |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * org.apache.usergrid.security.tokens.TokenService#removeTokens(org.apache.usergrid.security |
| * .AuthPrincipalInfo) |
| */ |
| @Override |
| public void removeTokens( AuthPrincipalInfo principal ) throws Exception { |
| List<UUID> tokenIds = getTokenUUIDS( principal ); |
| |
| Mutator<ByteBuffer> batch = createMutator( cassandra.getSystemKeyspace(), be ); |
| |
| for ( UUID tokenId : tokenIds ) { |
| batch.addDeletion( bytebuffer( tokenId ), TOKENS_CF ); |
| } |
| |
| batch.addDeletion( principalKey( principal ), PRINCIPAL_TOKEN_CF ); |
| |
| batch.execute(); |
| } |
| |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * org.apache.usergrid.security.tokens.TokenService#revokeToken(java.lang.String) |
| */ |
| @Override |
| public void revokeToken( String token ) { |
| |
| TokenInfo info; |
| |
| try { |
| info = getTokenInfo( token ); |
| } |
| catch ( Exception e ) { |
| logger.error( "Unable to find token with the specified value ignoring request. Value : {}", token ); |
| return; |
| } |
| |
| UUID tokenId = info.getUuid(); |
| |
| Mutator<ByteBuffer> batch = createMutator( cassandra.getSystemKeyspace(), be ); |
| |
| // clean up the link in the principal -> token index if the principal is |
| // on the token |
| if ( info.getPrincipal() != null ) { |
| batch.addDeletion( principalKey( info.getPrincipal() ), PRINCIPAL_TOKEN_CF, bytebuffer( tokenId ), |
| be ); |
| } |
| |
| // remove the token from the tokens cf |
| batch.addDeletion( bytebuffer( tokenId ), TOKENS_CF ); |
| |
| batch.execute(); |
| } |
| |
| |
| private TokenInfo getTokenInfo( UUID uuid ) throws Exception { |
| if ( uuid == null ) { |
| throw new InvalidTokenException( "No token specified" ); |
| } |
| Map<String, ByteBuffer> columns = getColumnMap( cassandra |
| .getColumns( cassandra.getSystemKeyspace(), TOKENS_CF, uuid, TOKEN_PROPERTIES, se, |
| be ) ); |
| if ( !hasKeys( columns, REQUIRED_TOKEN_PROPERTIES ) ) { |
| throw new InvalidTokenException( "Token not found in database" ); |
| } |
| String type = string( columns.get( TOKEN_TYPE ) ); |
| long created = getLong( columns.get( TOKEN_CREATED ) ); |
| long accessed = getLong( columns.get( TOKEN_ACCESSED ) ); |
| long inactive = getLong( columns.get( TOKEN_INACTIVE ) ); |
| long duration = getLong( columns.get( TOKEN_DURATION ) ); |
| String principalTypeStr = string( columns.get( TOKEN_PRINCIPAL_TYPE ) ); |
| AuthPrincipalType principalType = null; |
| if ( principalTypeStr != null ) { |
| try { |
| principalType = AuthPrincipalType.valueOf( principalTypeStr.toUpperCase() ); |
| } |
| catch ( IllegalArgumentException e ) { |
| } |
| } |
| AuthPrincipalInfo principal = null; |
| if ( principalType != null ) { |
| UUID entityId = uuid( columns.get( TOKEN_ENTITY ) ); |
| UUID appId = uuid( columns.get( TOKEN_APPLICATION ) ); |
| principal = new AuthPrincipalInfo( principalType, entityId, appId ); |
| } |
| @SuppressWarnings("unchecked") Map<String, Object> state = |
| ( Map<String, Object> ) JsonUtils.fromByteBuffer( columns.get( TOKEN_STATE ) ); |
| return new TokenInfo( uuid, type, created, accessed, inactive, duration, principal, state ); |
| } |
| |
| |
| private void putTokenInfo( TokenInfo tokenInfo ) throws Exception { |
| |
| ByteBuffer tokenUUID = bytebuffer( tokenInfo.getUuid() ); |
| |
| Keyspace ko = cassandra.getSystemKeyspace(); |
| |
| Mutator<ByteBuffer> m = createMutator( ko, be ); |
| |
| int ttl = calcTokenTime( tokenInfo.getDuration() ); |
| |
| m.addInsertion( tokenUUID, TOKENS_CF, |
| createColumn( TOKEN_UUID, bytebuffer( tokenInfo.getUuid() ), ttl, se, be ) ); |
| m.addInsertion( tokenUUID, TOKENS_CF, |
| createColumn( TOKEN_TYPE, bytebuffer( tokenInfo.getType() ), ttl, se, be ) ); |
| m.addInsertion( tokenUUID, TOKENS_CF, |
| createColumn( TOKEN_CREATED, bytebuffer( tokenInfo.getCreated() ), ttl, se, be ) ); |
| m.addInsertion( tokenUUID, TOKENS_CF, |
| createColumn( TOKEN_ACCESSED, bytebuffer( tokenInfo.getAccessed() ), ttl, se, be ) ); |
| m.addInsertion( tokenUUID, TOKENS_CF, |
| createColumn( TOKEN_INACTIVE, bytebuffer( tokenInfo.getInactive() ), ttl, se, be ) ); |
| m.addInsertion( tokenUUID, TOKENS_CF, |
| createColumn( TOKEN_DURATION, bytebuffer( tokenInfo.getDuration() ), ttl, se, be ) ); |
| |
| if ( tokenInfo.getPrincipal() != null ) { |
| |
| AuthPrincipalInfo principalInfo = tokenInfo.getPrincipal(); |
| |
| m.addInsertion( tokenUUID, TOKENS_CF, |
| createColumn( TOKEN_PRINCIPAL_TYPE, bytebuffer( principalInfo.getType().toString().toLowerCase() ), |
| ttl, se, be ) ); |
| m.addInsertion( tokenUUID, TOKENS_CF, |
| createColumn( TOKEN_ENTITY, bytebuffer( principalInfo.getUuid() ), ttl, se, be ) ); |
| m.addInsertion( tokenUUID, TOKENS_CF, |
| createColumn( TOKEN_APPLICATION, bytebuffer( principalInfo.getApplicationId() ), ttl, se, |
| be ) ); |
| |
| /* |
| * write to the PRINCIPAL+TOKEN The format is as follow |
| * |
| * appid+principalId+principalType :{ tokenuuid: 0x00} |
| */ |
| |
| ByteBuffer rowKey = principalKey( principalInfo ); |
| m.addInsertion( rowKey, PRINCIPAL_TOKEN_CF, createColumn( tokenUUID, HOLDER, ttl, be, be ) ); |
| } |
| |
| if ( tokenInfo.getState() != null ) { |
| m.addInsertion( tokenUUID, TOKENS_CF, |
| createColumn( TOKEN_STATE, JsonUtils.toByteBuffer( tokenInfo.getState() ), ttl, se, |
| be ) ); |
| } |
| |
| m.execute(); |
| } |
| |
| |
| /** Load all the token uuids for a principal info */ |
| private List<UUID> getTokenUUIDS( AuthPrincipalInfo principal ) throws Exception { |
| |
| ByteBuffer rowKey = principalKey( principal ); |
| |
| List<HColumn<ByteBuffer, ByteBuffer>> cols = cassandra |
| .getColumns( cassandra.getSystemKeyspace(), PRINCIPAL_TOKEN_CF, rowKey, null, null, Integer.MAX_VALUE, |
| false ); |
| |
| List<UUID> results = new ArrayList<UUID>( cols.size() ); |
| |
| for ( HColumn<ByteBuffer, ByteBuffer> col : cols ) { |
| results.add( uuid( col.getName() ) ); |
| } |
| |
| return results; |
| } |
| |
| |
| private ByteBuffer principalKey( AuthPrincipalInfo principalInfo ) { |
| // 66 bytes, 2 UUIDS + 2 chars for prefix |
| ByteBuffer buff = ByteBuffer.allocate( 32 * 2 + 2 ); |
| buff.put( bytes( principalInfo.getApplicationId() ) ); |
| buff.put( bytes( principalInfo.getUuid() ) ); |
| buff.put( bytes( principalInfo.getType().getPrefix() ) ); |
| buff.rewind(); |
| |
| return buff; |
| } |
| |
| |
| private UUID getUUIDForToken( String token ) throws ExpiredTokenException, BadTokenException { |
| |
| TokenCategory tokenCategory = TokenCategory.getFromBase64String( token ); |
| |
| if( tokenCategory == null){ |
| return null; |
| } |
| |
| byte[] bytes = decodeBase64( token.substring( TokenCategory.BASE64_PREFIX_LENGTH ) ); |
| UUID uuid = uuid( bytes ); |
| int i = 16; |
| long expires = Long.MAX_VALUE; |
| if ( tokenCategory.getExpires() ) { |
| expires = ByteBuffer.wrap( bytes, i, 8 ).getLong(); |
| i = 24; |
| } |
| ByteBuffer expected = ByteBuffer.allocate( 20 ); |
| expected.put( sha( tokenCategory.getPrefix() + uuid + tokenSecretSalt + expires ) ); |
| expected.rewind(); |
| ByteBuffer signature = ByteBuffer.wrap( bytes, i, 20 ); |
| |
| |
| if ( !signature.equals( expected ) ) { |
| throw new BadTokenException( "Invalid token signature" ); |
| } |
| |
| |
| long expirationDelta = System.currentTimeMillis() - expires; |
| |
| if ( expires != Long.MAX_VALUE && expirationDelta > 0 ) { |
| throw new ExpiredTokenException( String.format( "Token expired %d millisecons ago.", expirationDelta ) ); |
| } |
| return uuid; |
| } |
| |
| |
| @Override |
| public long getMaxTokenAge( String token ) { |
| TokenCategory tokenCategory = TokenCategory.getFromBase64String( token ); |
| byte[] bytes = decodeBase64( token.substring( TokenCategory.BASE64_PREFIX_LENGTH ) ); |
| UUID uuid = uuid( bytes ); |
| long timestamp = getTimestampInMillis( uuid ); |
| int i = 16; |
| if ( tokenCategory.getExpires() ) { |
| long expires = ByteBuffer.wrap( bytes, i, 8 ).getLong(); |
| return expires - timestamp; |
| } |
| return Long.MAX_VALUE; |
| } |
| |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * org.apache.usergrid.security.tokens.TokenService#getMaxTokenAgeInSeconds(java. |
| * lang.String) |
| */ |
| @Override |
| public long getMaxTokenAgeInSeconds( String token ) { |
| return getMaxTokenAge( token ) / 1000; |
| } |
| |
| |
| /** |
| * The maximum age a token can be saved for |
| * |
| * @return the maxPersistenceTokenAge |
| */ |
| public long getMaxPersistenceTokenAge() { |
| return maxPersistenceTokenAge; |
| } |
| |
| |
| @Autowired |
| @Qualifier("cassandraService") |
| public void setCassandraService( CassandraService cassandra ) { |
| this.cassandra = cassandra; |
| } |
| |
| |
| @Autowired |
| public void setEntityManagerFactory( EntityManagerFactory emf ) { |
| this.emf = emf; |
| } |
| |
| |
| private String getTokenForUUID( TokenInfo tokenInfo, TokenCategory tokenCategory, UUID uuid ) { |
| int l = 36; |
| if ( tokenCategory.getExpires() ) { |
| l += 8; |
| } |
| ByteBuffer bytes = ByteBuffer.allocate( l ); |
| bytes.put( bytes( uuid ) ); |
| long expires = Long.MAX_VALUE; |
| if ( tokenCategory.getExpires() ) { |
| expires = ( tokenInfo.getDuration() > 0 ) ? |
| UUIDUtils.getTimestampInMillis( uuid ) + ( tokenInfo.getDuration() ) : |
| UUIDUtils.getTimestampInMillis( uuid ) + getExpirationForTokenType( tokenCategory ); |
| bytes.putLong( expires ); |
| } |
| bytes.put( sha( tokenCategory.getPrefix() + uuid + tokenSecretSalt + expires ) ); |
| return tokenCategory.getBase64Prefix() + encodeBase64URLSafeString( bytes.array() ); |
| } |
| |
| |
| /** Calculate the column lifetime and account for long truncation to seconds */ |
| private int calcTokenTime( long time ) { |
| |
| long secondsDuration = time / 1000; |
| |
| int ttl = ( int ) secondsDuration; |
| |
| // we've had a ttl that's longer than Integer.MAX value |
| if ( ttl != secondsDuration ) { |
| // Something is up with cassandra... Setting ttl to integer.max |
| // makes the cols disappear..... |
| |
| // this should be the line below once this issue is fixed. |
| // https://issues.apache.org/jira/browse/CASSANDRA-4771 |
| // ttl = Integer.MAX_VALUE |
| |
| // take the max value of an int, and substract the system time off |
| // (in seconds) ,then arbitrarily remove another 120 seconds for good |
| // measure. |
| // Cass calcs the expiration time as |
| // "(System.currentTimeMillis() / 1000) + timeToLive);", so we need |
| // to play nice otherwise it blows up on persist |
| ttl = Integer.MAX_VALUE - ( int ) ( System.currentTimeMillis() / 1000 ) - 120; |
| } |
| // hard cap at the max in o.a.c.db.IColumn |
| if ( ttl > MAX_TTL ) { |
| ttl = MAX_TTL; |
| } |
| |
| return ttl; |
| } |
| |
| |
| private static final int MAX_TTL = 20 * 365 * 24 * 60 * 60; |
| } |