summaryrefslogtreecommitdiffstats
path: root/src/main/java/org/openslx/imagemaster/thrift/server/MasterServerHandler.java
blob: 60f4ccbcb1b6ee0395ccda8ebeccfd7a902e97b6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
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;
	}

}