/*************************************************************************
* (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
************************************************************************/
package com.eucalyptus.compute.common.internal.account;
import static com.eucalyptus.auth.policy.PolicySpec.IAM_RESOURCE_ROLE;
import static com.eucalyptus.auth.policy.PolicySpec.IAM_RESOURCE_USER;
import static com.eucalyptus.auth.policy.PolicySpec.VENDOR_IAM;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.eucalyptus.auth.policy.PolicySpec;
import com.eucalyptus.auth.policy.ern.Ern;
import com.eucalyptus.compute.common.IdFormatItemType;
import com.eucalyptus.compute.common.internal.account.IdentityIdFormat.IdResource;
import com.eucalyptus.compute.common.internal.account.IdentityIdFormat.IdType;
import com.eucalyptus.compute.common.internal.identifier.ResourceIdentifiers;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.EntityRestriction;
import com.eucalyptus.entities.TransactionResource;
import com.eucalyptus.util.Pair;
import com.eucalyptus.util.TypeMapper;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
/**
*
*/
@SuppressWarnings( { "Guava", "StaticPseudoFunctionalStyleMethod", "OptionalUsedAsFieldOrParameterType" } )
public class IdentityIdFormats {
private static final Set<String> LONG_ID_RESOURCES = ImmutableSet.copyOf(
Stream.of( IdResource.values( ) ).map( IdResource::name ).iterator( )
);
private static final Map<String,String> LONG_ID_PREFIX_TO_RESOURCE = ImmutableMap.copyOf(
Stream.of( IdResource.values( ) ).collect( Collectors.toMap( IdResource::prefix, IdResource::name ) )
);
private static final Map<String,String> LONG_ID_RESOURCE_TO_PREFIX = ImmutableMap.copyOf(
Stream.of( IdResource.values( ) ).collect( Collectors.toMap( IdResource::name, IdResource::prefix ) )
);
private static final LoadingCache<Pair<String,String>,Optional<Boolean>> LONG_ID_CONFIG_CACHE =
CacheBuilder.newBuilder( ).expireAfterWrite( 30, TimeUnit.SECONDS ).maximumSize( 1_000 ).build(
new CacheLoader<Pair<String,String>,Optional<Boolean>>() {
@Override
public Optional<Boolean> load( @Nonnull final Pair<String, String> identityArnAndResource ) {
final String identityArn = identityArnAndResource.getLeft( );
final String resource = identityArnAndResource.getRight( );
final IdResource idResource = IdResource.valueOf( resource );
final Optional<Pair<String, Pair<IdType, String>>> identityOption = IdentityIdFormats.tryParseIdentity( identityArn );
if ( identityOption.isPresent( ) ) {
final Optional<Boolean> identityLongIds = loadIdFormat(
identityOption.get( ).getLeft( ),
identityOption.get( ).getRight( ).getLeft( ),
identityOption.get( ).getRight( ).getRight( ),
idResource );
final Optional<Boolean> accountLongIds = identityOption.get( ).getRight( ).getLeft( ) == IdType.account ?
identityLongIds :
loadIdFormat(
identityOption.get( ).getLeft( ),
IdType.account,
identityOption.get( ).getLeft( ),
idResource );
return identityLongIds.or( accountLongIds );
}
return Optional.absent( );
}
}
);
public static String generate(
@Nonnull final String identityArn,
@Nonnull final String prefix
) {
final String resource = LONG_ID_PREFIX_TO_RESOURCE.get( prefix );
if ( resource != null && ResourceIdentifiers.useAccountLongIdentifierSettings( ) ) {
final Optional<Boolean> configuredLongIds =
LONG_ID_CONFIG_CACHE.getUnchecked( Pair.pair( identityArn, resource ) );
if ( configuredLongIds.isPresent( ) ) {
if ( configuredLongIds.get( ) ) {
return ResourceIdentifiers.generateLongString( prefix );
} else {
return ResourceIdentifiers.generateShortString( prefix );
}
}
}
return ResourceIdentifiers.generateString( prefix );
}
public static boolean isValidResource( final String resource ) {
return LONG_ID_RESOURCES.contains( resource );
}
@SuppressWarnings( "WeakerAccess" )
public static Optional<Pair<String,Pair<IdType,String>>> tryParseIdentity( final String identityArn ) {
return tryParseIdentity( null, identityArn );
}
public static Optional<Pair<String,Pair<IdType,String>>> tryParseIdentity(
final String accountNumber,
final String identityArn
) {
final Matcher accountRootArnMatcher = Pattern.compile( "arn:aws:iam::([0-9]{12}):root" ).matcher( identityArn );
if ( accountRootArnMatcher.matches( ) &&
( accountNumber == null || accountNumber.equals( accountRootArnMatcher.group( 1 ) ) ) ) {
return Optional.of( Pair.pair(
accountRootArnMatcher.group( 1 ),
Pair.pair( IdType.account, accountRootArnMatcher.group( 1 ) ) ) );
}
try {
final Ern ern = Ern.parse( identityArn );
if ( VENDOR_IAM.equals( ern.getService( ) ) && ern.getAccount( ) != null &&
( accountNumber == null || accountNumber.equals( ern.getAccount( ) ) ) ) {
if ( PolicySpec.qualifiedName( VENDOR_IAM, IAM_RESOURCE_ROLE ).equals( ern.getResourceType( ) ) ) {
return Optional.of( Pair.pair( ern.getAccount( ), Pair.pair( IdType.role, ern.getResourceName( ) ) ) );
} else if ( PolicySpec.qualifiedName( VENDOR_IAM, IAM_RESOURCE_USER ).equals( ern.getResourceType( ) ) ) {
return Optional.of( Pair.pair( ern.getAccount( ), Pair.pair( IdType.user, ern.getResourceName( ) ) ) );
}
}
} catch ( Exception e ) {
// not a valid user/role ARN
}
return Optional.absent( );
}
@SuppressWarnings( "WeakerAccess" )
public static Optional<Boolean> loadIdFormat(
final String accountNumber,
final IdType type,
final String id,
final IdResource resource
) {
//noinspection unused
try ( final TransactionResource tx = Entities.transactionFor( IdentityIdFormat.class ) ) {
final IdentityIdFormat idFormat = Entities.criteriaQuery( IdentityIdFormat.class )
.whereEqual( IdentityIdFormat_.accountNumber, accountNumber )
.whereEqual( IdentityIdFormat_.identityType, type )
.whereEqual( IdentityIdFormat_.identityFullName, id )
.whereEqual( IdentityIdFormat_.resource, resource )
.uniqueResult( );
return Optional.of( idFormat.getUseLongIdentifiers( ) );
} catch ( final NoSuchElementException e ) {
return Optional.absent( );
}
}
public static boolean saveIdFormat(
final String accountNumber,
final IdType type,
final String id,
final IdResource resource,
final Boolean useLongIds
) {
try {
Entities.asDistinctTransaction( IdentityIdFormat.class, new Function<Void, IdentityIdFormat>( ) {
@Nullable
@Override
public IdentityIdFormat apply( @Nullable final Void aVoid ) {
final IdentityIdFormat idFormat = Entities.criteriaQuery( IdentityIdFormat.class )
.whereEqual( IdentityIdFormat_.accountNumber, accountNumber )
.whereEqual( IdentityIdFormat_.identityType, type )
.whereEqual( IdentityIdFormat_.identityFullName, id )
.whereEqual( IdentityIdFormat_.resource, resource )
.uniqueResult( );
idFormat.setUseLongIdentifiers( useLongIds );
return idFormat;
}
} ).apply( null );
} catch ( NoSuchElementException e ) {
// so create
try ( final TransactionResource tx = Entities.transactionFor( IdentityIdFormat.class ) ) {
Entities.persist( IdentityIdFormat.create(
accountNumber,
type,
id,
resource,
useLongIds
) );
tx.commit( );
}
}
return true;
}
@SuppressWarnings( "WeakerAccess" )
public static List<IdentityIdFormat> listIdFormats(
final String accountNumber,
final IdType type,
final String id,
final Optional<IdResource> resource
) {
//noinspection unused
try ( final TransactionResource tx = Entities.transactionFor( IdentityIdFormat.class ) ) {
final EntityRestriction<IdentityIdFormat> resourceRestriction = resource.isPresent( ) ?
Entities.restriction( IdentityIdFormat.class ).equal( IdentityIdFormat_.resource, resource.get( ) ).build( ) :
Entities.restriction( IdentityIdFormat.class ).build( );
return Entities.criteriaQuery( IdentityIdFormat.class )
.whereEqual( IdentityIdFormat_.accountNumber, accountNumber )
.whereEqual( IdentityIdFormat_.identityType, type )
.whereEqual( IdentityIdFormat_.identityFullName, id )
.where( resourceRestriction )
.readonly( )
.list( );
}
}
public static List<IdentityIdFormat> listIdFormatsWithDefaults(
final String accountNumber,
final IdType type,
final String id,
final Optional<IdResource> resource
) {
final Set<IdResource> desiredResources = resource.isPresent( ) ?
resource.asSet( ) :
EnumSet.allOf( IdResource.class );
//noinspection unused
try ( final TransactionResource tx = Entities.transactionFor( IdentityIdFormat.class ) ) {
final List<IdentityIdFormat> formats = Lists.newArrayList( listIdFormats( accountNumber, type, id, resource ) );
final Set<IdResource> idSpecificResources =
formats.stream( ).map( IdentityIdFormat::getResource ).collect( Collectors.toSet( ) );
if ( type != IdType.account && !idSpecificResources.containsAll( desiredResources ) ) {
formats.addAll(
listIdFormats( accountNumber, IdType.account, accountNumber, resource ).stream( )
.filter( accountFormat -> !idSpecificResources.contains( accountFormat.getResource( ) ) )
.collect( Collectors.toList( ) ) );
}
final Set<IdResource> configuredResources =
formats.stream( ).map( IdentityIdFormat::getResource ).collect( Collectors.toSet( ) );
formats.addAll(
desiredResources.stream( )
.filter( configurableResource -> !configuredResources.contains( configurableResource ) )
.map( configurableResource -> IdentityIdFormat.create(
accountNumber,
IdType.account,
accountNumber,
configurableResource,
ResourceIdentifiers.useLongIdentifierForPrefix(
LONG_ID_RESOURCE_TO_PREFIX.get( configurableResource.name( ) )
)
) ).collect( Collectors.toList( ) ) );
formats.sort( Comparator.comparing( IdentityIdFormat::getResource ) );
return formats;
}
}
@TypeMapper
public enum IdentityIdFormatToIdFormatItemType implements Function<IdentityIdFormat,IdFormatItemType> {
INSTANCE;
@Nullable
@Override
public IdFormatItemType apply( @Nullable final IdentityIdFormat format ) {
return format == null ? null : new IdFormatItemType(
Objects.toString( format.getResource( ) ),
format.getUseLongIdentifiers( )
);
}
}
}