/************************************************************************* * Copyright 2009-2015 Eucalyptus Systems, Inc. * * 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/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. ************************************************************************/ package com.eucalyptus.auth.euare; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import javax.annotation.Nonnull; import com.eucalyptus.auth.AuthException; import com.eucalyptus.auth.euare.common.identity.Identity; import com.eucalyptus.auth.euare.persist.DatabasePrincipalProvider; import com.eucalyptus.auth.api.PrincipalProvider; import com.eucalyptus.auth.euare.identity.region.RegionConfigurationManager; import com.eucalyptus.auth.euare.identity.region.RegionConfigurations; import com.eucalyptus.auth.euare.identity.region.RegionInfo; import com.eucalyptus.auth.principal.AccountIdentifiers; import com.eucalyptus.auth.principal.InstanceProfile; import com.eucalyptus.auth.principal.OpenIdConnectProvider; import com.eucalyptus.auth.principal.Role; import com.eucalyptus.auth.principal.SecurityTokenContent; import com.eucalyptus.auth.principal.UserPrincipal; import com.eucalyptus.system.Threads; import com.eucalyptus.util.CollectionUtils; import com.eucalyptus.util.Either; import com.eucalyptus.util.Exceptions; import com.eucalyptus.util.FUtils; import com.eucalyptus.util.NonNullFunction; import com.eucalyptus.util.async.AsyncExceptions; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; /** * */ public class RegionDelegatingPrincipalProvider implements PrincipalProvider { private final PrincipalProvider localProvider = new DatabasePrincipalProvider( ); private final RegionConfigurationManager regionConfigurationManager = new RegionConfigurationManager( ); @Override public UserPrincipal lookupPrincipalByUserId( final String userId, final String nonce ) throws AuthException { return regionDispatchByIdentifier( userId, new NonNullFunction<PrincipalProvider, UserPrincipal>() { @Nonnull @Override public UserPrincipal apply( final PrincipalProvider principalProvider ) { try { return principalProvider.lookupPrincipalByUserId( userId, nonce ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } @Override public UserPrincipal lookupPrincipalByRoleId( final String roleId, final String nonce ) throws AuthException { return regionDispatchByIdentifier( roleId, new NonNullFunction<PrincipalProvider, UserPrincipal>() { @Nonnull @Override public UserPrincipal apply( final PrincipalProvider principalProvider ) { try { return principalProvider.lookupPrincipalByRoleId( roleId, nonce ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } @Override public UserPrincipal lookupPrincipalByAccessKeyId( final String keyId, final String nonce ) throws AuthException { return regionDispatchByIdentifier( keyId, new NonNullFunction<PrincipalProvider, UserPrincipal>() { @Nonnull @Override public UserPrincipal apply( final PrincipalProvider principalProvider ) { try { return principalProvider.lookupPrincipalByAccessKeyId( keyId, nonce ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } @Override public UserPrincipal lookupPrincipalByCertificateId( final String certificateId ) throws AuthException { return regionDispatchAndReduce( new NonNullFunction<PrincipalProvider, Either<AuthException, UserPrincipal>>() { @Nonnull @Override public Either<AuthException, UserPrincipal> apply( final PrincipalProvider principalProvider ) { try { return Either.right( principalProvider.lookupPrincipalByCertificateId( certificateId ) ); } catch ( AuthException e ) { return Either.left( e ); } } } ); } @Override public UserPrincipal lookupPrincipalByCanonicalId( final String canonicalId ) throws AuthException { return regionDispatchAndReduce( new NonNullFunction<PrincipalProvider, Either<AuthException, UserPrincipal>>() { @Nonnull @Override public Either<AuthException, UserPrincipal> apply( final PrincipalProvider principalProvider ) { try { return Either.right( principalProvider.lookupPrincipalByCanonicalId( canonicalId ) ); } catch ( AuthException e ) { return Either.left( e ); } } } ); } @Override public UserPrincipal lookupPrincipalByAccountNumber( final String accountNumber ) throws AuthException { return regionDispatchByAccountNumber( accountNumber, new NonNullFunction<PrincipalProvider, UserPrincipal>() { @Nonnull @Override public UserPrincipal apply( final PrincipalProvider principalProvider ) { try { return principalProvider.lookupPrincipalByAccountNumber( accountNumber ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } @Override public UserPrincipal lookupPrincipalByAccountNumberAndUsername( final String accountNumber, final String name ) throws AuthException { return regionDispatchByAccountNumber( accountNumber, new NonNullFunction<PrincipalProvider, UserPrincipal>() { @Nonnull @Override public UserPrincipal apply( final PrincipalProvider principalProvider ) { try { return principalProvider.lookupPrincipalByAccountNumberAndUsername( accountNumber, name ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } @Override public UserPrincipal lookupCachedPrincipalByUserId( final UserPrincipal cached, final String userId, final String nonce ) throws AuthException { return regionDispatchByIdentifier( userId, new NonNullFunction<PrincipalProvider, UserPrincipal>( ) { @Nonnull @Override public UserPrincipal apply( final PrincipalProvider principalProvider ) { try { return principalProvider.lookupCachedPrincipalByUserId( cached, userId, nonce ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } @Override public UserPrincipal lookupCachedPrincipalByRoleId( final UserPrincipal cached, final String roleId, final String nonce ) throws AuthException { return regionDispatchByIdentifier( roleId, new NonNullFunction<PrincipalProvider, UserPrincipal>( ) { @Nonnull @Override public UserPrincipal apply( final PrincipalProvider principalProvider ) { try { return principalProvider.lookupCachedPrincipalByRoleId( cached, roleId, nonce ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } @Override public UserPrincipal lookupCachedPrincipalByAccessKeyId( final UserPrincipal cached, final String keyId, final String nonce ) throws AuthException { return regionDispatchByIdentifier( keyId, new NonNullFunction<PrincipalProvider, UserPrincipal>( ) { @Nonnull @Override public UserPrincipal apply( final PrincipalProvider principalProvider ) { try { return principalProvider.lookupCachedPrincipalByAccessKeyId( cached, keyId, nonce ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } @Override public UserPrincipal lookupCachedPrincipalByCertificateId( final UserPrincipal cached, final String certificateId ) throws AuthException { if ( cached != null ) { return regionDispatchByAccountNumber( cached.getAccountNumber( ), new NonNullFunction<PrincipalProvider, UserPrincipal>( ) { @Nonnull @Override public UserPrincipal apply( final PrincipalProvider principalProvider ) { try { return principalProvider.lookupCachedPrincipalByCertificateId( cached, certificateId ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } else { return regionDispatchAndReduce( new NonNullFunction<PrincipalProvider, Either<AuthException, UserPrincipal>>() { @Nonnull @Override public Either<AuthException, UserPrincipal> apply( final PrincipalProvider principalProvider ) { try { return Either.right( principalProvider.lookupCachedPrincipalByCertificateId( null, certificateId ) ); } catch ( AuthException e ) { return Either.left( e ); } } } ); } } @Override public UserPrincipal lookupCachedPrincipalByAccountNumber( final UserPrincipal cached, final String accountNumber ) throws AuthException { return regionDispatchByAccountNumber( accountNumber, new NonNullFunction<PrincipalProvider, UserPrincipal>( ) { @Nonnull @Override public UserPrincipal apply( final PrincipalProvider principalProvider ) { try { return principalProvider.lookupCachedPrincipalByAccountNumber( cached, accountNumber ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } @Override public AccountIdentifiers lookupAccountIdentifiersByAlias( final String alias ) throws AuthException { return regionDispatchAndReduce( new NonNullFunction<PrincipalProvider, Either<AuthException, AccountIdentifiers>>() { @Nonnull @Override public Either<AuthException, AccountIdentifiers> apply( final PrincipalProvider principalProvider ) { try { return Either.right( principalProvider.lookupAccountIdentifiersByAlias( alias ) ); } catch ( AuthException e ) { return Either.left( e ); } } } ); } @Override public AccountIdentifiers lookupAccountIdentifiersByCanonicalId( final String canonicalId ) throws AuthException { return regionDispatchAndReduce( new NonNullFunction<PrincipalProvider, Either<AuthException,AccountIdentifiers>>( ) { @Nonnull @Override public Either<AuthException,AccountIdentifiers> apply( final PrincipalProvider principalProvider ) { try { return Either.right( principalProvider.lookupAccountIdentifiersByCanonicalId( canonicalId ) ); } catch ( AuthException e ) { return Either.left( e ); } } } ); } @Override public AccountIdentifiers lookupAccountIdentifiersByEmail( final String email ) throws AuthException { return regionDispatchAndReduce( new NonNullFunction<PrincipalProvider, Either<AuthException,AccountIdentifiers>>( ) { @Nonnull @Override public Either<AuthException,AccountIdentifiers> apply( final PrincipalProvider principalProvider ) { try { return Either.right( principalProvider.lookupAccountIdentifiersByEmail( email ) ); } catch ( AuthException e ) { return Either.left( e ); } } } ); } @Override public List<AccountIdentifiers> listAccountIdentifiersByAliasMatch( final String aliasExpression ) throws AuthException { return regionDispatchAndReduce( new NonNullFunction<PrincipalProvider, Either<AuthException,List<AccountIdentifiers>>>( ) { @Nonnull @Override public Either<AuthException,List<AccountIdentifiers>> apply( final PrincipalProvider principalProvider ) { try { return Either.right( principalProvider.listAccountIdentifiersByAliasMatch( aliasExpression ) ); } catch ( AuthException e ) { return Either.left( e ); } } }, Lists.<AccountIdentifiers>newArrayList( ), CollectionUtils.<AccountIdentifiers,List<AccountIdentifiers>>addAll( ) ); } @Override public InstanceProfile lookupInstanceProfileByName( final String accountNumber, final String name ) throws AuthException { return regionDispatchByAccountNumber( accountNumber, new NonNullFunction<PrincipalProvider, InstanceProfile>() { @Nonnull @Override public InstanceProfile apply( final PrincipalProvider principalProvider ) { try { return principalProvider.lookupInstanceProfileByName( accountNumber, name ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } @Override public Role lookupRoleByName( final String accountNumber, final String name ) throws AuthException { return regionDispatchByAccountNumber( accountNumber, new NonNullFunction<PrincipalProvider, Role>() { @Nonnull @Override public Role apply( final PrincipalProvider principalProvider ) { try { return principalProvider.lookupRoleByName( accountNumber, name ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } @Override public OpenIdConnectProvider lookupOidcProviderByUrl( final String accountNumber, final String url ) throws AuthException { return regionDispatchByAccountNumber( accountNumber, new NonNullFunction<PrincipalProvider, OpenIdConnectProvider>() { @Nonnull @Override public OpenIdConnectProvider apply( final PrincipalProvider principalProvider ) { try { return principalProvider.lookupOidcProviderByUrl( accountNumber, url ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } @Override public SecurityTokenContent decodeSecurityToken( final String accessKeyIdentifier, final String securityToken ) throws AuthException { return regionDispatchByIdentifier( accessKeyIdentifier, new NonNullFunction<PrincipalProvider, SecurityTokenContent>() { @Nonnull @Override public SecurityTokenContent apply( final PrincipalProvider principalProvider ) { try { return principalProvider.decodeSecurityToken( accessKeyIdentifier, securityToken ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } @Override public void reserveGlobalName( final String namespace, final String name, final Integer duration, final String clientToken ) throws AuthException { final int numberOfRegions = Iterables.size( regionConfigurationManager.getRegionInfos( ) ); final Integer successes = numberOfRegions <= 1 ? 1 : // skip if only a local region regionDispatchAndReduce( new NonNullFunction<PrincipalProvider, Either<AuthException,String>>( ) { @Nonnull @Override public Either<AuthException,String> apply( final PrincipalProvider identityProvider ) { try { identityProvider.reserveGlobalName( namespace, name, duration, clientToken ); return Either.right( "" ); } catch ( AuthException e ) { if ( AsyncExceptions.isWebServiceErrorCode( e, AuthException.CONFLICT ) || AuthException.CONFLICT.equals( e.getMessage( ) ) ) { return Either.left( e ); } else { throw Exceptions.toUndeclared( e ); } } } }, 0, CollectionUtils.<String>count( Predicates.notNull( ) ) ); if ( successes < numberOfRegions ) { throw new AuthException( AuthException.CONFLICT ); } } @Override public X509Certificate getCertificateByAccountNumber( final String accountNumber ) throws AuthException { return regionDispatchByAccountNumber( accountNumber, new NonNullFunction<PrincipalProvider, X509Certificate>() { @Nonnull @Override public X509Certificate apply( final PrincipalProvider principalProvider ) { try { return principalProvider.getCertificateByAccountNumber( accountNumber ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } @Override public X509Certificate signCertificate( final String accountNumber, final RSAPublicKey publicKey, final String principal, final int expiryInDays ) throws AuthException { return regionDispatchByAccountNumber( accountNumber, new NonNullFunction<PrincipalProvider, X509Certificate>() { @Nonnull @Override public X509Certificate apply( final PrincipalProvider principalProvider ) { try { return principalProvider.signCertificate( accountNumber, publicKey, principal, expiryInDays ); } catch ( AuthException e ) { throw Exceptions.toUndeclared( e ); } } } ); } private <R> R regionDispatchByIdentifier( final String identifier, final NonNullFunction<PrincipalProvider, R> invoker ) throws AuthException { return regionDispatch( regionConfigurationManager.getRegionByIdentifier( identifier ), invoker ); } private <R> R regionDispatchByAccountNumber( final String accountNumber, final NonNullFunction<PrincipalProvider, R> invoker ) throws AuthException { return regionDispatch( regionConfigurationManager.getRegionByAccountNumber( accountNumber ), invoker ); } private <R> R regionDispatch( final Optional<RegionInfo> regionInfo, final NonNullFunction<PrincipalProvider, R> invoker ) throws AuthException { try { if ( regionInfo.isPresent( ) && !RegionConfigurations.getRegionName( ).asSet( ).contains( regionInfo.get( ).getName( ) ) ) { final Optional<Set<String>> endpoints = regionInfo.transform( RegionInfo.serviceEndpoints( "identity" ) ); if ( endpoints.isPresent( ) && !endpoints.get( ).isEmpty( ) ) { final PrincipalProvider remoteProvider = new RemotePrincipalProvider( endpoints.get( ) ); return invoker.apply( remoteProvider ); } return invoker.apply( localProvider ); } else { return invoker.apply( localProvider ); } } catch ( final RuntimeException e ) { Exceptions.findAndRethrow( e, AuthException.class ); throw e; } } private <R> R regionDispatchAndReduce( final NonNullFunction<PrincipalProvider, Either<AuthException,R>> invoker ) throws AuthException { return regionDispatchAndReduce( invoker, null, CollectionUtils.<R>unique( ) ); } private <R,I> I regionDispatchAndReduce( final NonNullFunction<PrincipalProvider, Either<AuthException,R>> invoker, final I initial, final Function<I,Function<R,I>> reducer ) throws AuthException { try { final Iterable<RegionInfo> regionInfos = regionConfigurationManager.getRegionInfos( ); final List<Either<AuthException,R>> regionResults = Lists.newArrayList( ); regionResults.add( invoker.apply( localProvider ) ); if ( !Iterables.isEmpty( regionInfos ) ) { final List<Future<Either<AuthException,R>>> regionResultFutures = Lists.newArrayList( ); withRegions: for ( final RegionInfo regionInfo : regionInfos ) { if ( !RegionConfigurations.getRegionName( ).asSet( ).contains( regionInfo.getName( ) ) ) { for ( final RegionInfo.RegionService service : regionInfo.getServices( ) ) { if ( "identity".equals( service.getType( ) ) ) { final PrincipalProvider remoteProvider = new RemotePrincipalProvider( service.getEndpoints( ) ); regionResultFutures.add( Threads.enqueue( Identity.class, RegionDelegatingPrincipalProvider.class, FUtils.cpartial( invoker, remoteProvider ) ) ); continue withRegions; } } } } for ( final Future<Either<AuthException,R>> future : regionResultFutures ) { try { regionResults.add( future.get( ) ); } catch ( final InterruptedException e ) { throw new AuthException( "Interrupted" ); } catch ( final ExecutionException e ) { throw new RuntimeException( e ); // Any AuthException caught and unwrapped below } } } final Iterable<R> successResults = Optional.presentInstances( Iterables.transform( regionResults, Either.<AuthException,R>rightOption( ) ) ); if ( Iterables.size( successResults ) > 0 ) { return CollectionUtils.reduce( successResults, initial, reducer ); } throw Iterables.get( Optional.presentInstances( Iterables.transform( regionResults, Either.<AuthException,R>leftOption( ) ) ), 0 ); } catch ( final RuntimeException e ) { Exceptions.findAndRethrow( e, AuthException.class ); throw e; } } }