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<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<ImagePublishData> 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<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" );
// 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<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;
}
}