summaryrefslogblamecommitdiffstats
path: root/src/main/java/org/openslx/imagemaster/thrift/server/MasterServerHandler.java
blob: 3bdbd3fbfe2c1da8d33d11edce86eeea78b76a78 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12

                                              
                    








                                                  
                                    

                                                        
                                                      
                                                      
                                                      














                                                                
                                                    


                                                 
                                               




                                                       





                                                             

                                                                  





                                                                  
                                             
                                         
                                                   



















































                                                                                                                                    











                                                                                      













                                                                                                                          
                                                                                   

                                                                            























                                                                                                                          




















                                                                                       
                                             























































                                                                                                                                                








                                                                                                                          


                 
                                                                                                                      

                                                                                                        



                                                                                                                                          


                                                                                                              








                                                                                                                                      



                                                                                                                                    















                                                                                                                                

                                                                                                                                       

                                                                













                                                                                                                                                      





                                                                                                                             



                                                                                                                   
                                                                                                           



                                                       
                                                                   

                                










                                                                                                                                                 







                                                                                                                 



                                                                                           
                                                                                                                            
         


















                                                                                                                                          







                                                                                
                                                                                                                  








                                                                                      
                                                                                                                  








                                                                              
                                                                                                                  

































                                                                                                                                                  























                                                                                                                              




























                                                                                                                         
 
package org.openslx.imagemaster.thrift.server;

import java.io.File;
import java.nio.ByteBuffer;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.apache.thrift.TException;
import org.openslx.bwlp.thrift.iface.AuthorizationError;
import org.openslx.bwlp.thrift.iface.ClientSessionData;
import org.openslx.bwlp.thrift.iface.ImageDetailsRead;
import org.openslx.bwlp.thrift.iface.ImagePublishData;
import org.openslx.bwlp.thrift.iface.ImageSummaryRead;
import org.openslx.bwlp.thrift.iface.InvocationError;
import org.openslx.bwlp.thrift.iface.MasterServer;
import org.openslx.bwlp.thrift.iface.MasterSoftware;
import org.openslx.bwlp.thrift.iface.MasterTag;
import org.openslx.bwlp.thrift.iface.OperatingSystem;
import org.openslx.bwlp.thrift.iface.Organization;
import org.openslx.bwlp.thrift.iface.Satellite;
import org.openslx.bwlp.thrift.iface.ServerSessionData;
import org.openslx.bwlp.thrift.iface.SessionData;
import org.openslx.bwlp.thrift.iface.TAuthorizationException;
import org.openslx.bwlp.thrift.iface.TInvalidTokenException;
import org.openslx.bwlp.thrift.iface.TInvocationException;
import org.openslx.bwlp.thrift.iface.TNotFoundException;
import org.openslx.bwlp.thrift.iface.TTransferRejectedException;
import org.openslx.bwlp.thrift.iface.TransferInformation;
import org.openslx.bwlp.thrift.iface.TransferStatus;
import org.openslx.bwlp.thrift.iface.UserInfo;
import org.openslx.bwlp.thrift.iface.Virtualizer;
import org.openslx.encryption.AsymKeyHolder;
import org.openslx.filetransfer.util.ChunkList;
import org.openslx.filetransfer.util.FileChunk;
import org.openslx.imagemaster.Globals;
import org.openslx.imagemaster.db.Database;
import org.openslx.imagemaster.db.mappers.DbImage;
import org.openslx.imagemaster.db.mappers.DbImageBlock;
import org.openslx.imagemaster.db.mappers.DbOrganization;
import org.openslx.imagemaster.db.mappers.DbOsVirt;
import org.openslx.imagemaster.db.mappers.DbPendingSatellite;
import org.openslx.imagemaster.db.mappers.DbSatellite;
import org.openslx.imagemaster.db.mappers.DbUser;
import org.openslx.imagemaster.db.models.LocalSatellite;
import org.openslx.imagemaster.serverconnection.ConnectionHandler;
import org.openslx.imagemaster.serverconnection.IncomingTransfer;
import org.openslx.imagemaster.serversession.ServerAuthenticator;
import org.openslx.imagemaster.serversession.ServerSession;
import org.openslx.imagemaster.serversession.ServerSessionManager;
import org.openslx.imagemaster.session.Authenticator;
import org.openslx.imagemaster.session.Session;
import org.openslx.imagemaster.session.SessionManager;
import org.openslx.imagemaster.util.UserUtil;
import org.openslx.imagemaster.util.Util;
import org.openslx.thrifthelper.ImagePublishDataEx;

public class MasterServerHandler implements MasterServer.Iface
{

	private static final Logger LOGGER = Logger.getLogger( MasterServerHandler.class );

	@Override
	public boolean ping()
	{
		return true;
	}

	@Override
	public SessionData authenticate( String login, String password ) throws TAuthorizationException, TInvocationException
	{
		ClientSessionData csd = localAccountLogin( login, password );
		String serverAddress = null;
		if ( csd.satellites != null && !csd.satellites.isEmpty() ) {
			for ( Satellite sat : csd.satellites ) {
				if ( sat.addressList == null || sat.addressList.isEmpty() )
					continue;
				if ( serverAddress == null || ( sat.displayName != null && sat.displayName.equals( "default" ) ) ) {
					serverAddress = sat.addressList.get( 0 );
				}
			}
		}
		return new SessionData( csd.sessionId, csd.authToken, serverAddress );
	}

	/**
	 * Request for authentication
	 * 
	 * @param login The user's login in the form "user@organization.com"
	 * @param password user's password
	 * @return SessionData struct with session id/token iff login successful
	 * @throws TAuthorizationException if login not successful
	 */
	@Override
	public ClientSessionData localAccountLogin( String login, String password )
			throws TAuthorizationException, TInvocationException
	{
		if ( login == null || password == null ) {
			throw new TAuthorizationException(
					AuthorizationError.INVALID_CREDENTIALS,
					"Empty username or password!" );
		}
		final UserInfo user = Authenticator.authenticate( login, password );

		final Session session = new Session( user );
		return SessionManager.addSession( session );
	}

	/**
	 * User tells us which satellite they connected to.
	 */
	@Override
	public void setUsedSatellite( String sessionId, String satelliteName )
	{
		Session session = SessionManager.getSessionFromSessionId( sessionId );
		if ( session == null )
			return;
		//session.setUsedSatellite( satelliteName );
	}

	@Override
	public List<UserInfo> findUser( String sessionId, String organizationId, String searchTerm )
			throws TAuthorizationException, TInvocationException
	{
		// Needs to be a logged in user
		if ( SessionManager.getSessionFromSessionId( sessionId ) == null )
			throw new TAuthorizationException( AuthorizationError.NOT_AUTHENTICATED, "Session ID not valid" );
		// Search string needs to be at least 2 characters (FIXME: quick and dirty ignoring LIKE chars) 
		if ( searchTerm == null || searchTerm.length() < 2 || searchTerm.replaceAll( "[%_]", "" ).length() < 2 )
			return new ArrayList<>( 0 );
		return DbUser.findUser( organizationId, searchTerm );
	}

	@Override
	public List<ImageSummaryRead> getPublicImages( String sessionId, int page )
			throws TAuthorizationException, TInvocationException
	{
		Session session = SessionManager.getSessionFromSessionId( sessionId );
		if ( session == null )
			throw new TAuthorizationException( AuthorizationError.NOT_AUTHENTICATED, "Session ID not valid" );
		UserUtil.assertTutor( session.getUserInfo() );
		try {
			return DbImage.getPublicList( page );
		} catch ( SQLException e ) {
			throw new TInvocationException( InvocationError.INTERNAL_SERVER_ERROR, "Database failure" );
		}
	}

	@Override
	public ImageDetailsRead getImageDetails( String sessionId, String imageBaseId )
			throws TAuthorizationException, TNotFoundException, TInvocationException
	{
		Session session = SessionManager.getSessionFromSessionId( sessionId );
		if ( session == null )
			throw new TAuthorizationException( AuthorizationError.NOT_AUTHENTICATED, "Session ID not valid" );
		UserUtil.assertTutor( session.getUserInfo() );
		try {
			return DbImage.getImageDetails( imageBaseId );
		} catch ( SQLException e ) {
			throw new TInvocationException( InvocationError.INTERNAL_SERVER_ERROR, "Database failure" );
		}
	}

	@Override
	public void invalidateSession( String sessionId ) throws TInvalidTokenException
	{
		SessionManager.invalidate( sessionId );
	}

	/**
	 * Request information about user for given token
	 * 
	 * @param token a user's token
	 * @return UserInfo struct for given token's user
	 * @throws InvalidTokenException if no user matches the given token
	 */
	@Override
	public UserInfo getUserFromToken( String token ) throws TInvalidTokenException
	{
		final Session session = SessionManager.getSessionFromToken( token );
		if ( session == null )
			throw new TInvalidTokenException();
		return session.getUserInfo();
	}

	@Override
	public boolean isServerAuthenticated( String serverSessionId )
	{
		return ( ServerSessionManager.getSession( serverSessionId ) != null );
	}

	/**
	 * Start the server authentication of a uni/hs satellite server.
	 * 
	 * @param organization the organization that the server belongs to
	 * @return a random string that needs to be encrypted with the private
	 *         key of the requesting satellite server
	 * @throws ServerAuthenticationException when organization is invalid/unknown
	 */
	@Override
	public ByteBuffer startServerAuthentication( int satelliteId ) throws TAuthorizationException, TInvocationException
	{
		LocalSatellite satellite = DbSatellite.get( satelliteId );
		if ( satellite == null )
			throw new TAuthorizationException( AuthorizationError.INVALID_ORGANIZATION, "Unknown satellite id: " + satelliteId );
		if ( satellite.getPubkey() == null )
			throw new TAuthorizationException( AuthorizationError.INVALID_KEY, "There is no public key known for your satellite." );
		return ServerAuthenticator.startServerAuthentication( satelliteId );
	}

	/**
	 * Authenticate the uni/hs satellite server with the encrypted string.
	 * 
	 * @param organization the organization that the server belongs to
	 * @param challengeResponse the encrypted string
	 * @return session data iff the authentication was successful
	 */
	@Override
	public ServerSessionData serverAuthenticate( int satelliteId, ByteBuffer challengeResponse )
			throws TAuthorizationException, TInvocationException
	{
		if ( challengeResponse == null )
			throw new TAuthorizationException( AuthorizationError.INVALID_ORGANIZATION, "Empty organization or challengeResponse" );
		LocalSatellite satellite = DbSatellite.get( satelliteId );
		if ( satellite == null )
			throw new TAuthorizationException( AuthorizationError.INVALID_ORGANIZATION, "Unknown satellite id: " + satelliteId );
		if ( satellite.getPubkey() == null )
			throw new TAuthorizationException( AuthorizationError.INVALID_KEY, "There is no public key known for your satellite." );

		ServerAuthenticator.serverAuthenticate( satellite, challengeResponse );

		final ServerSession session = new ServerSession( satellite );
		return ServerSessionManager.addSession( session );
	}

	@Override
	public ImagePublishData getImageData( String serverSessionId, String imageVersionId )
			throws TAuthorizationException, TInvocationException, TNotFoundException
	{
		Session session = SessionManager.getSessionFromSessionIdOrToken( serverSessionId );
		if ( session == null )
			throw new TAuthorizationException( AuthorizationError.INVALID_TOKEN, "Unknown session id/token" );
		UserUtil.assertTutor( session.getUserInfo() );
		try {
			return DbImage.getImageVersion( imageVersionId );
		} catch ( SQLException e ) {
			throw new TInvocationException( InvocationError.INTERNAL_SERVER_ERROR, "Database error" );
		}
	}

	@Override
	public TransferInformation submitImage( String userToken, ImagePublishData img, List<ByteBuffer> blockHashes )
			throws TAuthorizationException, TInvocationException, TTransferRejectedException
	{
		// Valid submit session?
		Session session = SessionManager.getSessionFromToken( userToken );
		if ( session == null )
			throw new TAuthorizationException( AuthorizationError.INVALID_TOKEN, "Given user token not known to the server" );
		UserUtil.assertTutor( session.getUserInfo() );
		img.owner = UserUtil.getFirstPublishingUser( img.owner, img.uploader, session.getUserInfo() );
		img.uploader = UserUtil.getFirstPublishingUserOrDummy( img.uploader, session.getUserInfo() );
		// check image data
		if ( Util.isEmpty( img.imageName ) )
			throw new TInvocationException( InvocationError.INVALID_DATA, "Image name not set" );
		if ( img.fileSize <= 0 )
			throw new TInvocationException( InvocationError.INVALID_DATA, "File size is too small" );
		if ( !Util.isUUID( img.imageBaseId ) )
			throw new TInvocationException( InvocationError.MISSING_DATA, "ImagePublishData has invalid imageBaseId" );
		if ( !Util.isUUID( img.imageVersionId ) )
			throw new TInvocationException( InvocationError.MISSING_DATA, "ImagePublishData has invalid imageVersionId" );
		if ( img.owner == null || img.owner.userId == null )
			throw new TInvocationException( InvocationError.MISSING_DATA, "Missing owner or owner is anonymous" );
		if ( img.uploader == null || img.uploader.userId == null )
			throw new TInvocationException( InvocationError.MISSING_DATA, "Missing uploader or uploader is anonymous" );
		// check for complete block hash list
		boolean listComplete = false;
		if ( blockHashes != null && blockHashes.size() == FileChunk.fileSizeToChunkCount( img.fileSize ) ) {
			listComplete = true;
			for ( ByteBuffer bb : blockHashes ) {
				if ( bb == null || bb.remaining() != FileChunk.SHA1_LENGTH ) {
					listComplete = false;
					break;
				}
			}
		}
		if ( !listComplete )
			throw new TInvocationException( InvocationError.INVALID_DATA, "Chunk hash list missing or incomplete" );
		// Check if an upload is already assigned
		IncomingTransfer existingUpload = ConnectionHandler.getExistingUpload( img, blockHashes );
		if ( existingUpload != null ) {
			LOGGER.info( "Satellite tried to register already existing upload for version " + img.imageVersionId + " - is "
					+ existingUpload.getId() );
			return existingUpload.getTransferInfo();
		}
		// Check if version already exists
		ImagePublishDataEx existing;
		try {
			existing = DbImage.getImageVersion( img.imageVersionId );
			if ( existing != null ) {
				if ( existing.fileSize != img.fileSize )
					throw new TInvocationException( InvocationError.INVALID_DATA, "Image already exists; file size mismatch" );
				List<ByteBuffer> existingHashes = DbImageBlock.getBlockHashes( img.imageVersionId );
				if ( !ChunkList.hashListsEqualBbBb( blockHashes, existingHashes ) )
					throw new TInvocationException( InvocationError.INVALID_DATA, "Image already exists; block hashes mismatch" );
			}
		} catch ( SQLException e ) {
			throw new TInvocationException( InvocationError.INTERNAL_SERVER_ERROR, "Internal database error" );
		}
		// No existing upload - create new one
		// checks that hit the db
		if ( !DbOsVirt.osExists( img.osId ) )
			throw new TInvocationException( InvocationError.INVALID_DATA, "Content operating system not set" );
		if ( !DbOsVirt.virtExists( img.virtId ) )
			throw new TInvocationException( InvocationError.INVALID_DATA, "Content virtualizer system not set" );
		// Make sure we have a destination to write to
		if ( !new File( Globals.getImageDir() ).isDirectory() )
			throw new TInvocationException( InvocationError.INTERNAL_SERVER_ERROR, "Storage offline" );
		// Try to register an upload
		IncomingTransfer transfer = ConnectionHandler.registerUpload( img, blockHashes, existing );
		try {
			DbImage.createImageBase( img );
		} catch ( TException t ) {
			transfer.cancel();
			ConnectionHandler.removeUpload( transfer );
			throw t;
		}
		if ( existing == null ) {
			try {
				DbImage.createImageVersion( img, transfer.getRelativePath() );
			} catch ( SQLException e1 ) {
				transfer.cancel();
				ConnectionHandler.removeUpload( transfer );
				if ( Database.isDuplicateKeyException( e1 ) ) {
					throw new TInvocationException( InvocationError.INVALID_DATA, "The image already exists on the server" );
				} else {
					throw new TInvocationException( InvocationError.INTERNAL_SERVER_ERROR, "Database error" );
				}
			}
		}
		try {
			DbImageBlock.insertChunkList( img.imageVersionId, transfer.getChunks().getAll(), true );
		} catch ( SQLException e ) {
			LOGGER.warn( "Could not insert block hashes of image " + img.imageVersionId + " to db" );
		}
		return transfer.getTransferInfo();
	}

	@Override
	public TransferInformation downloadImage( String sessionId, String imageVersionId )
			throws TAuthorizationException, TInvocationException, TNotFoundException, TTransferRejectedException
	{
		// Valid submit session?
		Session session = SessionManager.getSessionFromToken( sessionId );
		if ( session == null )
			throw new TAuthorizationException( AuthorizationError.INVALID_TOKEN, "Given user token not known to the server" );
		UserUtil.assertTutor( session.getUserInfo() );
		ImagePublishDataEx img;
		List<ByteBuffer> blockHashes;
		try {
			img = DbImage.getImageVersion( imageVersionId );
			blockHashes = DbImageBlock.getBlockHashes( img.imageVersionId );
		} catch ( SQLException e ) {
			throw new TInvocationException( InvocationError.INTERNAL_SERVER_ERROR, "Database error" );
		}
		if ( img == null || !img.exIsValid )
			throw new TNotFoundException();
		TransferInformation ti = ConnectionHandler.registerDownload( img );
		ti.machineDescription = img.machineDescription;
		ti.blockHashes = blockHashes;
		return ti;
	}

	@Override
	public List<Organization> getOrganizations() throws TInvocationException
	{
		try {
			return DbOrganization.getAll();
		} catch ( SQLException e ) {
			throw new TInvocationException( InvocationError.INTERNAL_SERVER_ERROR, "Database error" );
		}
	}

	@Override
	public List<OperatingSystem> getOperatingSystems() throws TInvocationException
	{
		try {
			return DbOsVirt.getOsList();
		} catch ( SQLException e ) {
			throw new TInvocationException( InvocationError.INTERNAL_SERVER_ERROR, "Database error" );
		}
	}

	@Override
	public List<Virtualizer> getVirtualizers() throws TInvocationException
	{
		try {
			return DbOsVirt.getVirtualizerList();
		} catch ( SQLException e ) {
			throw new TInvocationException( InvocationError.INTERNAL_SERVER_ERROR, "Database error" );
		}
	}

	@Override
	public List<MasterTag> getTags( long startDate ) throws TInvocationException
	{
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<MasterSoftware> getSoftware( long startDate ) throws TInvocationException
	{
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int registerSatellite( String userToken, String displayName, List<String> addresses, String modulus,
			String exponent, ByteBuffer certSha256 ) throws TInvocationException
	{
		if ( userToken == null || exponent == null || modulus == null )
			throw new TInvocationException( InvocationError.MISSING_DATA, "A required parameter is null" );
		final Session session = SessionManager.getSessionFromToken( userToken );
		if ( session == null || session.getUserInfo() == null )
			throw new TInvocationException( InvocationError.UNKNOWN_USER, "Not a valid user token" );
		String organizationId = session.getUserInfo().organizationId;
		Key newKey;
		try {
			newKey = new AsymKeyHolder( null, Util.tryToParseBigInt( exponent ), Util.tryToParseBigInt( modulus ) ).getPublicKey();
		} catch ( NoSuchAlgorithmException | InvalidKeySpecException e ) {
			LOGGER.warn( "Invalid public key in registerOrganization for " + organizationId + " (By " + session.getLogin() + ")", e );
			throw new TInvocationException( InvocationError.INVALID_DATA, "Cannot reconstruct public key" );
		}
		LocalSatellite existing = DbSatellite.get( organizationId, displayName );
		if ( existing != null ) {
			Key existingKey = existing.getPubkey();
			if ( existingKey != null && Util.keysEqual( newKey, existingKey ) )
				return existing.satelliteId;
		}
		try {
			return DbPendingSatellite.add( session.getUserInfo(), displayName, addresses, modulus, exponent );
		} catch ( SQLException e ) {
			throw new TInvocationException();
		}
	}

	@Override
	public boolean updateSatellite( String serverSessionId, String displayName, List<String> addresses )
			throws TAuthorizationException, TInvocationException
	{
		ServerSession session = ServerSessionManager.getSession( serverSessionId );
		if ( session == null )
			throw new TAuthorizationException( AuthorizationError.NOT_AUTHENTICATED, "No valid serverSessionId" );
		// TODO
		return false;
	}

	@Override
	public TransferStatus queryUploadStatus( String uploadToken ) throws TInvalidTokenException
	{
		IncomingTransfer upload = ConnectionHandler.getUploadByToken( uploadToken );
		if ( upload == null )
			throw new TInvalidTokenException();
		return upload.getStatus();
	}

	@Override
	public UserInfo getUser( String userToken, String userId )
			throws TAuthorizationException, TNotFoundException, TInvocationException
	{
		Session session = SessionManager.getSessionFromToken( userToken );
		if ( session == null )
			throw new TAuthorizationException( AuthorizationError.NOT_AUTHENTICATED, "No valid user token" );
		UserInfo userInfo = session.getUserInfo();
		UserUtil.assertTutor( userInfo );
		UserInfo queriedUser;
		try {
			queriedUser = DbUser.getUserInfo( userToken );
		} catch ( SQLException e ) {
			throw new TInvocationException( InvocationError.INTERNAL_SERVER_ERROR, "Database broken" );
		}
		if ( UserUtil.getFirstPublishingUser( queriedUser ) == null )
			throw new TNotFoundException( "Unknown userid" );
		return queriedUser;
	}

}