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.ImagePublishData; 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.UserInfo; import org.openslx.bwlp.thrift.iface.Virtualizer; import org.openslx.encryption.AsymKeyHolder; 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.db.models.LocalUser; 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.Util; 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 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 getPublicImages( String sessionId, int page ) throws TAuthorizationException, TInvocationException { // TODO Auto-generated method stub return null; } @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 { // TODO Auto-generated method stub return null; } @Override public TransferInformation submitImage( String userToken, ImagePublishData img, List 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" ); // 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.user == null || img.user.userId == null ) throw new TInvocationException( InvocationError.MISSING_DATA, "Missing user id" ); // 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 ) { return existingUpload.getTransferInfo(); } // 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" ); try { LocalUser user = DbUser.forUserId( img.user.userId ); if ( user == null ) { user = DbUser.forUserId( session.getUserInfo().userId ); if ( user != null ) { img.user = user.toUserInfo(); } } if ( user == null ) throw new TInvocationException( InvocationError.UNKNOWN_USER, "Unknown user id " + img.user.userId ); if ( user.isAnonymous() ) throw new TInvocationException( InvocationError.UNKNOWN_USER, "The owner of the image does not participate in image exchange" ); } catch ( SQLException e ) { throw new TInvocationException( InvocationError.INTERNAL_SERVER_ERROR, "Database error" ); } // 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 ); try { DbImage.createImageBase( img ); } catch ( TException t ) { transfer.cancel(); throw t; } try { DbImage.createImageVersion( img, transfer.getRelativePath() ); } catch ( SQLException e1 ) { transfer.cancel(); 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 { // TODO Auto-generated method stub return null; } @Override public List getOrganizations() throws TInvocationException { try { return DbOrganization.getAll(); } catch ( SQLException e ) { throw new TInvocationException( InvocationError.INTERNAL_SERVER_ERROR, "Database error" ); } } @Override public List getOperatingSystems() throws TInvocationException { try { return DbOsVirt.getOsList(); } catch ( SQLException e ) { throw new TInvocationException( InvocationError.INTERNAL_SERVER_ERROR, "Database error" ); } } @Override public List getVirtualizers() throws TInvocationException { try { return DbOsVirt.getVirtualizerList(); } catch ( SQLException e ) { throw new TInvocationException( InvocationError.INTERNAL_SERVER_ERROR, "Database error" ); } } @Override public List getTags( long startDate ) throws TInvocationException { // TODO Auto-generated method stub return null; } @Override public List getSoftware( long startDate ) throws TInvocationException { // TODO Auto-generated method stub return null; } @Override public int registerSatellite( String userToken, String displayName, List 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 addresses ) throws TAuthorizationException, TInvocationException { ServerSession session = ServerSessionManager.getSession( serverSessionId ); if ( session == null ) throw new TAuthorizationException( AuthorizationError.NOT_AUTHENTICATED, "No valid serverSessionId" ); // TODO return false; } }