package org.openslx.imagemaster.server; import java.nio.ByteBuffer; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; import java.util.List; import org.apache.log4j.Logger; import org.openslx.encryption.AsymKeyHolder; import org.openslx.imagemaster.db.DbImage; import org.openslx.imagemaster.db.DbPendingSatellite; import org.openslx.imagemaster.db.DbSatellite; import org.openslx.imagemaster.db.DbUser; import org.openslx.imagemaster.serverconnection.ImageProcessor; import org.openslx.imagemaster.serversession.ServerAuthenticator; import org.openslx.imagemaster.serversession.ServerSession; import org.openslx.imagemaster.serversession.ServerSessionManager; import org.openslx.imagemaster.serversession.ServerUser; import org.openslx.imagemaster.session.Authenticator; import org.openslx.imagemaster.session.Session; import org.openslx.imagemaster.session.SessionManager; import org.openslx.imagemaster.session.User; import org.openslx.imagemaster.thrift.iface.AuthenticationError; import org.openslx.imagemaster.thrift.iface.AuthenticationException; import org.openslx.imagemaster.thrift.iface.AuthorizationError; import org.openslx.imagemaster.thrift.iface.AuthorizationException; import org.openslx.imagemaster.thrift.iface.DownloadData; import org.openslx.imagemaster.thrift.iface.ImageData; import org.openslx.imagemaster.thrift.iface.ImageDataError; import org.openslx.imagemaster.thrift.iface.ImageDataException; import org.openslx.imagemaster.thrift.iface.InvalidTokenException; import org.openslx.imagemaster.thrift.iface.OrganizationData; import org.openslx.imagemaster.thrift.iface.ServerSessionData; import org.openslx.imagemaster.thrift.iface.SessionData; import org.openslx.imagemaster.thrift.iface.UploadData; import org.openslx.imagemaster.thrift.iface.UploadException; import org.openslx.imagemaster.thrift.iface.UserInfo; import org.openslx.imagemaster.util.Util; /** * API Server This is where all the requests from the outside arrive. We don't * handle them directly in the Thrift handlers, as we might be adding other APIs * later, like JSON/SOAP/REST/HTTP/XML or some other stuff. They'd all just * interface with this static class here. Note that we use the exceptions from * the thrift interface that you can simply catch in any other API handler and * eg. transform into error codes, if the API doesn't support exceptions. * * This will be accessed from multiple threads, so use synchronization when * needed (or in doubt) */ public class ApiServer { private static final Logger LOG = Logger.getLogger( ApiServer.class ); /** * 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 AuthenticationException if login not successful */ public static SessionData authenticate( String login, String password ) throws AuthenticationException { if ( login == null || password == null ) { throw new AuthenticationException( AuthenticationError.INVALID_CREDENTIALS, "Empty username or password!" ); } final User user = Authenticator.authenticate( login, password ); final Session session = new Session( user ); return SessionManager.addSession( session ); } /** * 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 */ public static UserInfo getUserFromToken( String token ) throws InvalidTokenException { final Session session = SessionManager.getSessionFromToken( token ); if ( session == null ) throw new InvalidTokenException(); return new UserInfo( session.getLogin(), session.getFirstName(), session.getLastName(), session.getEMail(), session.getOrgenizationId() ); } public static UploadData submitImage( String serverSessionId, ImageData imageDescription, List crcSums ) throws AuthorizationException, ImageDataException, UploadException { // first check session of server if ( ServerSessionManager.getSession( serverSessionId ) == null ) { throw new AuthorizationException( AuthorizationError.NOT_AUTHENTICATED, "No valid serverSessionId" ); } // then let the image processor decide what to do return ImageProcessor.getUploadInfos( imageDescription, crcSums ); } public static DownloadData getImage( String uuid, String serverSessionId ) throws AuthorizationException, ImageDataException { // first check session of server if ( ServerSessionManager.getSession( serverSessionId ) == null ) { throw new AuthorizationException( AuthorizationError.NOT_AUTHENTICATED, "No valid serverSessionId" ); } if ( !DbImage.exists( uuid ) ) { throw new ImageDataException( ImageDataError.UNKNOWN_IMAGE, "UUID is not known by this server." ); } // then let the image processor decide what to do return ImageProcessor.getDownloadInfos( uuid ); } /** * 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 */ public static ByteBuffer startServerAuthentication( String organizationId ) throws AuthenticationException { if ( organizationId == null || organizationId.isEmpty() ) throw new AuthenticationException( AuthenticationError.INVALID_ORGANIZATION, "Empty organization" ); DbSatellite satellite = DbSatellite.fromOrganizationId( organizationId ); if ( satellite == null ) throw new AuthenticationException( AuthenticationError.INVALID_ORGANIZATION, "Unknown organization: '" + organizationId + "'" ); if ( satellite.getPubkey() == null ) throw new AuthenticationException( AuthenticationError.INVALID_KEY, "There is no public key known for your organization." ); return ServerAuthenticator.startServerAuthentication( organizationId ); } /** * 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 * @throws AuthenticationException */ public static ServerSessionData serverAuthenticate( String organizationId, ByteBuffer challengeResponse ) throws AuthenticationException { if ( organizationId == null || challengeResponse == null ) { throw new AuthenticationException( AuthenticationError.INVALID_ORGANIZATION, "Empty organization or challengeResponse" ); } DbSatellite satellite = DbSatellite.fromOrganizationId( organizationId ); if ( satellite == null ) throw new AuthenticationException( AuthenticationError.INVALID_ORGANIZATION, "Unknown organization" ); if ( satellite.getPubkey() == null ) throw new AuthenticationException( AuthenticationError.INVALID_KEY, "There is no public key known for your organization." ); final ServerUser serverUser = ServerAuthenticator.serverAuthenticate( satellite, challengeResponse ); final ServerSession session = new ServerSession( serverUser ); return ServerSessionManager.addSession( session ); } public static boolean isServerAuthenticated( String serverSessionId ) { return ( ServerSessionManager.getSession( serverSessionId ) != null ); } public static boolean publishUser( String serverSessionId, UserInfo user ) throws AuthorizationException { // Check session. if ( SessionManager.getSessionFromSessionId( serverSessionId ) == null ) { throw new AuthorizationException( AuthorizationError.NOT_AUTHENTICATED, "Session ID not valid" ); } if ( DbUser.forLogin( user.userId ) == null ) { // User not known by server. Insert into server database. return DbUser.insertOrUpdate( user ); } // Else user is already known by server. Do nothing. return true; } public static List findUser( String sessionId, String organizationId, String searchTerm ) throws AuthorizationException { // Needs to be a logged in user if ( SessionManager.getSessionFromSessionId( sessionId ) == null ) throw new AuthorizationException( 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 ); } public static List getOrganizations() { return DbSatellite.asOrganizationDataList(); } public static List getPublicImages( String sessionId, int page ) throws AuthorizationException { if ( SessionManager.getSessionFromSessionId( sessionId ) == null ) throw new AuthorizationException( AuthorizationError.NOT_AUTHENTICATED, "Session ID not valid" ); return DbImage.asImageDataList( page * 100, ( page + 1 ) * 100 ); } public static boolean registerSatellite( String organizationId, String address, String modulus, String exponent ) { if ( organizationId == null || address == null || exponent == null || modulus == null ) return false; Key newKey; try { newKey = new AsymKeyHolder( null, Util.tryToParseBigInt( exponent ), Util.tryToParseBigInt( modulus ) ).getPublicKey(); } catch ( NoSuchAlgorithmException | InvalidKeySpecException e ) { LOG.warn( "Invalid public key in registerOrganization for " + organizationId + " (" + address + ")", e ); return false; } if ( newKey == null ) { LOG.warn( "Uninstantiable public key in registerOrganization for " + organizationId + " (" + address + ")" ); return false; } DbSatellite existing = DbSatellite.fromSuffix( organizationId ); if ( existing != null ) { Key existingKey = existing.getPubkey(); if ( existingKey != null && Util.keysEqual( newKey, existingKey ) ) return true; } return DbPendingSatellite.add( organizationId, address, modulus, exponent ); } public static boolean updateSatelliteAddress( String serverSessionId, String address ) throws AuthorizationException { if ( ServerSessionManager.getSession( serverSessionId ) == null ) throw new AuthorizationException( AuthorizationError.NOT_AUTHENTICATED, "No valid serverSessionId" ); // TODO: Implement return false; } }