/*************************************************************************
* 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.persist;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.apache.log4j.Logger;
import com.eucalyptus.auth.Accounts;
import com.eucalyptus.auth.AuthException;
import com.eucalyptus.auth.Debugging;
import com.eucalyptus.auth.PolicyParseException;
import com.eucalyptus.auth.euare.checker.InvalidValueException;
import com.eucalyptus.auth.euare.checker.ValueChecker;
import com.eucalyptus.auth.euare.checker.ValueCheckerFactory;
import com.eucalyptus.auth.euare.persist.entities.AccountEntity_;
import com.eucalyptus.auth.euare.persist.entities.GroupEntity;
import com.eucalyptus.auth.euare.persist.entities.GroupEntity_;
import com.eucalyptus.auth.euare.persist.entities.InstanceProfileEntity;
import com.eucalyptus.auth.euare.persist.entities.InstanceProfileEntity_;
import com.eucalyptus.auth.euare.persist.entities.ManagedPolicyEntity;
import com.eucalyptus.auth.euare.persist.entities.PolicyEntity;
import com.eucalyptus.auth.euare.persist.entities.RoleEntity;
import com.eucalyptus.auth.euare.persist.entities.RoleEntity_;
import com.eucalyptus.auth.euare.principal.EuareAccount;
import com.eucalyptus.auth.euare.principal.EuareManagedPolicy;
import com.eucalyptus.auth.euare.principal.EuareRole;
import com.eucalyptus.auth.policy.PolicyParser;
import com.eucalyptus.auth.policy.PolicyPolicy;
import com.eucalyptus.auth.principal.AccountFullName;
import com.eucalyptus.auth.euare.principal.EuareInstanceProfile;
import com.eucalyptus.auth.principal.Policy;
import com.eucalyptus.auth.principal.PolicyScope;
import com.eucalyptus.auth.principal.PolicyVersion;
import com.eucalyptus.auth.principal.PolicyVersions;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionResource;
import com.eucalyptus.util.Callback;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.auth.principal.OwnerFullName;
import com.eucalyptus.util.Tx;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.Lists;
/**
* Role implementation backed by RoleEntity
*/
public class DatabaseRoleProxy implements EuareRole {
private static final long serialVersionUID = 1L;
private static Logger LOG = Logger.getLogger( DatabaseRoleProxy.class );
private static final ValueChecker POLICY_NAME_CHECKER = ValueCheckerFactory.createPolicyNameChecker( );
private RoleEntity delegate;
private transient Supplier<String> accountNumberSupplier =
DatabaseAuthUtils.getAccountNumberSupplier( this );
public DatabaseRoleProxy( RoleEntity delegate ) {
this.delegate = delegate;
}
@Override
public String getDisplayName() {
return Accounts.getRoleFullName( this );
}
@Override
public OwnerFullName getOwner() {
try {
return AccountFullName.getInstance( getAccount().getAccountNumber() );
} catch ( AuthException e ) {
throw Exceptions.toUndeclared( e );
}
}
@Override
public String toString( ) {
final StringBuilder sb = new StringBuilder( );
try {
DatabaseAuthUtils.invokeUnique( RoleEntity.class, RoleEntity_.roleId, this.delegate.getRoleId(), new Tx<RoleEntity>( ) {
@Override
public void fire( RoleEntity t ) {
sb.append( t.toString( ) );
}
} );
} catch ( ExecutionException e ) {
Debugging.logError( LOG, e, "Failed to toString for " + this.delegate );
}
return sb.toString();
}
@Override
public String getRoleId() {
return this.delegate.getRoleId();
}
@Override
public String getRoleArn( ) throws AuthException {
return Accounts.getRoleArn( this );
}
@Override
public String getName() {
return this.delegate.getName();
}
@Override
public String getPath() {
return this.delegate.getPath();
}
@Override
public String getSecret() {
return this.delegate.getSecret();
}
@Override
public PolicyVersion getPolicy() {
try {
return PolicyVersions.policyVersion( PolicyScope.Resource, getRoleArn( ) ).apply( getAssumeRolePolicy() );
} catch ( Exception e ) {
throw Exceptions.toUndeclared( e );
}
}
@Override
public Policy getAssumeRolePolicy() throws AuthException {
if ( Entities.isReadable( delegate.getAssumeRolePolicy( ) ) ) {
return new DatabasePolicyProxy( delegate.getAssumeRolePolicy( ) );
} else {
final List<Policy> results = Lists.newArrayList( );
dbCallback( "getAssumeRolePolicy", new Callback<RoleEntity>( ) {
@Override
public void fire( final RoleEntity roleEntity ) {
results.add( new DatabasePolicyProxy( roleEntity.getAssumeRolePolicy( ) ) );
}
} );
return results.get( 0 );
}
}
@Override
public Policy setAssumeRolePolicy( final String policy ) throws AuthException, PolicyParseException {
final PolicyPolicy parsedPolicy = PolicyParser.getResourceInstance().parse( policy );
final PolicyEntity policyEntity = PolicyEntity.create( "assume-role-policy-for-" + getRoleId(), parsedPolicy.getPolicyVersion( ), policy );
try ( final TransactionResource db = Entities.transactionFor( RoleEntity.class ) ) {
final RoleEntity roleEntity = getRoleEntity( );
// Due to https://hibernate.onjira.com/browse/HHH-6484 we must explicitly delete the old policy
final PolicyEntity oldAssumeRolePolicy = roleEntity.getAssumeRolePolicy();
roleEntity.setAssumeRolePolicy( policyEntity );
Entities.delete( oldAssumeRolePolicy );
final PolicyEntity persistedPolicyEntity = Entities.persist( policyEntity );
db.commit( );
return new DatabasePolicyProxy( persistedPolicyEntity );
} catch ( Exception e ) {
Debugging.logError( LOG, e, "Failed to set assume role policy for " + this.delegate.getName( ) );
throw new AuthException( "Failed to set assume role policy", e );
}
}
@Override
public Date getCreationTimestamp() {
return delegate.getCreationTimestamp();
}
@Override
public String getAccountNumber() throws AuthException {
return DatabaseAuthUtils.extract( accountNumberSupplier );
}
public EuareAccount getAccount( ) throws AuthException {
if ( Entities.isReadable( delegate.getAccount( ) ) ) {
return new DatabaseAccountProxy( delegate.getAccount( ) );
} else {
final List<EuareAccount> results = Lists.newArrayList( );
dbCallback( "getAccount", new Callback<RoleEntity>( ) {
@Override
public void fire( final RoleEntity roleEntity ) {
results.add( new DatabaseAccountProxy( roleEntity.getAccount( ) ) );
}
} );
return results.get( 0 );
}
}
@Override
public List<Policy> getPolicies() throws AuthException {
final List<Policy> results = Lists.newArrayList( );
try ( final TransactionResource db = Entities.transactionFor( RoleEntity.class ) ) {
final RoleEntity role = getRoleEntity( );
for ( final PolicyEntity policyEntity : role.getPolicies( ) ) {
results.add( new DatabasePolicyProxy( policyEntity ) );
}
return results;
} catch ( Exception e ) {
Debugging.logError( LOG, e, "Failed to get policies for " + this.delegate );
throw new AuthException( "Failed to get policies", e );
}
}
@Override
public Policy addPolicy( String name, String policy ) throws AuthException, PolicyParseException {
return storePolicy( name, policy, /*allowUpdate*/ false );
}
@Override
public Policy putPolicy( String name, String policy ) throws AuthException, PolicyParseException {
return storePolicy( name, policy, /*allowUpdate*/ true );
}
private Policy storePolicy( String name, String policy, boolean allowUpdate ) throws AuthException, PolicyParseException {
check( POLICY_NAME_CHECKER, AuthException.INVALID_NAME, name );
if ( DatabaseAuthUtils.policyNameinList( name, this.getPolicies( ) ) && !allowUpdate ) {
Debugging.logError( LOG, null, "Policy name already used: " + name );
throw new AuthException( AuthException.INVALID_NAME );
}
final PolicyPolicy policyPolicy = PolicyParser.getInstance().parse( policy );
final PolicyEntity parsedPolicy = PolicyEntity.create( name, policyPolicy.getPolicyVersion( ), policy );
try ( final TransactionResource db = Entities.transactionFor( RoleEntity.class ) ) {
final RoleEntity roleEntity = getRoleEntity( );
final PolicyEntity remove = DatabaseAuthUtils.removeNamedPolicy( roleEntity.getPolicies( ), name );
if ( remove != null ) {
Entities.delete( remove );
}
parsedPolicy.setRole( roleEntity );
roleEntity.getPolicies( ).add( parsedPolicy );
final PolicyEntity persistedPolicyEntity = Entities.persist( parsedPolicy );
db.commit( );
return new DatabasePolicyProxy( persistedPolicyEntity );
} catch ( Exception e ) {
Debugging.logError( LOG, e, "Failed to attach policy for " + this.delegate.getName( ) );
throw new AuthException( "Failed to attach policy", e );
}
}
@Override
public void removePolicy( final String name ) throws AuthException {
if ( Strings.isNullOrEmpty( name ) ) {
throw new AuthException( AuthException.EMPTY_POLICY_NAME );
}
try ( final TransactionResource db = Entities.transactionFor( RoleEntity.class ) ) {
final RoleEntity roleEntity = getRoleEntity( );
final PolicyEntity policy = DatabaseAuthUtils.removeNamedPolicy( roleEntity.getPolicies(), name );
if ( policy != null ) Entities.delete( policy );
db.commit( );
} catch ( Exception e ) {
Debugging.logError( LOG, e, "Failed to remove policy " + name + " in " + this.delegate );
throw new AuthException( "Failed to remove policy", e );
}
}
@Override
public List<EuareManagedPolicy> getAttachedPolicies( ) {
final List<EuareManagedPolicy> results = Lists.newArrayList( );
try {
DatabaseAuthUtils.invokeUnique( RoleEntity.class, RoleEntity_.roleId, getRoleId( ), new Tx<RoleEntity>( ) {
@Override
public void fire( final RoleEntity roleEntity ) {
for ( ManagedPolicyEntity p : roleEntity.getAttachedPolicies( ) ) {
results.add( new DatabaseManagedPolicyProxy( p ) );
}
}
} );
} catch ( ExecutionException e ) {
Debugging.logError( LOG, e, "Failed to getAttachedPolicies for " + this.delegate );
}
return results;
}
@Override
public void attachPolicy( final EuareManagedPolicy policy ) throws AuthException {
try {
final String accountNumber = policy.getAccountNumber( );
DatabaseAuthUtils.invokeUnique( RoleEntity.class, RoleEntity_.roleId, getRoleId( ), new Tx<RoleEntity>( ) {
@Override
public void fire( final RoleEntity roleEntity ) {
final ManagedPolicyEntity policyEntity = Entities.criteriaQuery(
ManagedPolicyEntity.exampleWithName( accountNumber, policy.getName( ) )
).uniqueResult( );
if ( roleEntity.getAttachedPolicies( ).add( policyEntity ) ) {
policyEntity.setAttachmentCount( DatabaseAuthUtils.countAttachments( policyEntity ) );
}
}
} );
} catch ( ExecutionException e ) {
Debugging.logError( LOG, e, "Failed to attachPolicy for " + this.delegate );
}
}
@Override
public void detachPolicy( final EuareManagedPolicy policy ) throws AuthException {
try {
DatabaseAuthUtils.invokeUnique( RoleEntity.class, RoleEntity_.roleId, getRoleId( ), new Tx<RoleEntity>( ) {
@Override
public void fire( final RoleEntity roleEntity ) {
ManagedPolicyEntity policyEntity = null;
for ( final ManagedPolicyEntity attachedPolicy : roleEntity.getAttachedPolicies( ) ) {
if ( attachedPolicy.getPolicyId( ).equals( policy.getPolicyId( ) ) ) {
policyEntity = attachedPolicy;
break;
}
}
if ( policyEntity != null ) {
roleEntity.getAttachedPolicies( ).remove( policyEntity );
policyEntity.setAttachmentCount( DatabaseAuthUtils.countAttachments( policyEntity ) );
} else {
throw Exceptions.toUndeclared( new AuthException( AuthException.NO_SUCH_POLICY ) );
}
}
} );
} catch ( ExecutionException e ) {
Exceptions.findAndRethrow( e, AuthException.class );
Debugging.logError( LOG, e, "Failed to detachPolicy for " + this.delegate );
}
}
@Override
public List<EuareInstanceProfile> getInstanceProfiles() throws AuthException {
final List<EuareInstanceProfile> results = Lists.newArrayList( );
try ( final TransactionResource db = Entities.transactionFor( InstanceProfileEntity.class ) ) {
@SuppressWarnings( "unchecked" )
List<InstanceProfileEntity> instanceProfiles = Entities
.criteriaQuery( InstanceProfileEntity.class )
.join( InstanceProfileEntity_.role ).whereEqual( RoleEntity_.name, this.delegate.getName( ) )
.join( RoleEntity_.account ).whereEqual( AccountEntity_.accountNumber, this.delegate.getAccount( ).getAccountNumber( ) )
.list( );
for ( final InstanceProfileEntity instanceProfile : instanceProfiles ) {
results.add( new DatabaseInstanceProfileProxy( instanceProfile ) );
}
return results;
} catch ( Exception e ) {
Debugging.logError( LOG, e, "Failed to get instance profiles for " + this.delegate.getName( ) );
throw new AuthException( "Failed to get instance profiles", e );
}
}
private void check( ValueChecker checker, String error, String value ) throws AuthException {
try {
checker.check( value );
} catch ( InvalidValueException e ) {
Debugging.logError( LOG, e, error + " " + value );
throw new AuthException( error, e );
}
}
private void dbCallback( final String description,
final Callback<RoleEntity> updateCallback ) throws AuthException {
try {
DatabaseAuthUtils.invokeUnique( RoleEntity.class, RoleEntity_.roleId, getRoleId(), new Tx<RoleEntity>( ) {
@Override
public void fire( final RoleEntity roleEntity ) {
updateCallback.fire( roleEntity );
}
} );
} catch ( ExecutionException e ) {
Debugging.logError( LOG, e, "Failed to " + description + " for " + this.delegate );
throw new AuthException( e );
}
}
private RoleEntity getRoleEntity( ) throws Exception {
return DatabaseAuthUtils.getUnique( RoleEntity.class, RoleEntity_.roleId, getRoleId() );
}
private void readObject( ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject( );
this.accountNumberSupplier = DatabaseAuthUtils.getAccountNumberSupplier( this );
}
}