summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSimon Rettberg2015-09-07 18:20:58 +0200
committerSimon Rettberg2015-09-07 18:20:58 +0200
commit2f140304dd193763b2aa9d509f972c6f23202e93 (patch)
tree8639a625543d78b35caccfe3dcdfa4740ba6d917 /src
parentStuff (diff)
downloadmasterserver-2f140304dd193763b2aa9d509f972c6f23202e93.tar.gz
masterserver-2f140304dd193763b2aa9d509f972c6f23202e93.tar.xz
masterserver-2f140304dd193763b2aa9d509f972c6f23202e93.zip
Start adapting to new DB/Thrift model
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/openslx/imagemaster/db/Database.java174
-rw-r--r--src/main/java/org/openslx/imagemaster/db/DbSatellite.java156
-rw-r--r--src/main/java/org/openslx/imagemaster/db/DbSatelliteSuffix.java18
-rw-r--r--src/main/java/org/openslx/imagemaster/db/DbUser.java107
-rw-r--r--src/main/java/org/openslx/imagemaster/db/LdapUser.java168
-rw-r--r--src/main/java/org/openslx/imagemaster/db/MySQL.java120
-rw-r--r--src/main/java/org/openslx/imagemaster/db/MysqlConnection.java78
-rw-r--r--src/main/java/org/openslx/imagemaster/db/MysqlStatement.java321
-rw-r--r--src/main/java/org/openslx/imagemaster/db/Paginator.java11
-rw-r--r--src/main/java/org/openslx/imagemaster/db/mappers/DbImage.java (renamed from src/main/java/org/openslx/imagemaster/db/DbImage.java)15
-rw-r--r--src/main/java/org/openslx/imagemaster/db/mappers/DbOperatingSystem.java36
-rw-r--r--src/main/java/org/openslx/imagemaster/db/mappers/DbOrganization.java137
-rw-r--r--src/main/java/org/openslx/imagemaster/db/mappers/DbPendingSatellite.java (renamed from src/main/java/org/openslx/imagemaster/db/DbPendingSatellite.java)3
-rw-r--r--src/main/java/org/openslx/imagemaster/db/mappers/DbUser.java108
-rw-r--r--src/main/java/org/openslx/imagemaster/db/models/LocalOrganization.java62
-rw-r--r--src/main/java/org/openslx/imagemaster/db/models/LocalUser.java (renamed from src/main/java/org/openslx/imagemaster/session/User.java)28
-rw-r--r--src/main/java/org/openslx/imagemaster/localrpc/JsonUser.java23
-rw-r--r--src/main/java/org/openslx/imagemaster/localrpc/NetworkHandler.java188
-rw-r--r--src/main/java/org/openslx/imagemaster/localrpc/ShibUser.java13
-rw-r--r--src/main/java/org/openslx/imagemaster/thrift/server/TBinaryProtocolSafe.java122
20 files changed, 1179 insertions, 709 deletions
diff --git a/src/main/java/org/openslx/imagemaster/db/Database.java b/src/main/java/org/openslx/imagemaster/db/Database.java
new file mode 100644
index 0000000..76c44fc
--- /dev/null
+++ b/src/main/java/org/openslx/imagemaster/db/Database.java
@@ -0,0 +1,174 @@
+package org.openslx.imagemaster.db;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.Properties;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.apache.log4j.Logger;
+import org.openslx.imagemaster.util.Util;
+
+public class Database
+{
+
+ private static final Logger LOGGER = Logger.getLogger( Database.class );
+ /**
+ * Pool of available connections.
+ */
+ private static final Queue<MysqlConnection> pool = new ConcurrentLinkedQueue<>();
+
+ /**
+ * Set of connections currently handed out.
+ */
+ private static final Set<MysqlConnection> busyConnections = Collections.newSetFromMap( new ConcurrentHashMap<MysqlConnection, Boolean>() );
+
+ private static final String host;
+ private static final String dbname;
+ private static final String user;
+ private static final String password;
+
+ /**
+ * Static initializer for setting up the database connection.
+ * This gets called implicitly as soon as the class loader loads
+ * the class. In most cases that happens when the class is being
+ * accessed for the first time during run time.
+ */
+ static
+ {
+ // Load connection info from class (TODO: Make pretty)
+ Properties properties = new Properties();
+ try {
+ final BufferedInputStream stream = new BufferedInputStream(
+ new FileInputStream(
+ "config/mysql.properties" ) );
+ properties.load( stream );
+ stream.close();
+ } catch ( FileNotFoundException e ) {
+ LOGGER.fatal( "config/mysql.properties not found!" );
+ System.exit( 1 );
+ } catch ( IOException e ) {
+ LOGGER.fatal( "Error reading from config/mysql.properties: " + e.getMessage() );
+ System.exit( 1 );
+ } catch ( Exception e ) {
+ LOGGER.fatal( "Generic error loading mysql properties file." );
+ e.printStackTrace();
+ System.exit( 1 );
+ }
+ host = properties.getProperty( "host" );
+ dbname = properties.getProperty( "db" );
+ user = properties.getProperty( "user" );
+ password = properties.getProperty( "password" );
+
+ Util.notNullFatal( host, "host not set in mysql properties" );
+ Util.notNullFatal( dbname, "db not set in mysql properties" );
+ Util.notNullFatal( user, "user not set in mysql properties" );
+ Util.notNullFatal( password, "password not set in mysql properties" );
+
+ try {
+ Class.forName( "com.mysql.jdbc.Driver" ).newInstance();
+ } catch ( InstantiationException | IllegalAccessException | ClassNotFoundException e ) {
+ LOGGER.fatal( "Cannot get mysql JDBC driver!", e );
+ System.exit( 1 );
+ }
+ }
+
+ /**
+ * Get a connection to the database. If there is a valid connection in the
+ * pool, it will be returned. Otherwise, a new connection is created. If
+ * there are more than 20 busy connections, <code>null</code> is returned.
+ *
+ * @return connection to database, or <code>null</code>
+ */
+ public static MysqlConnection getConnection()
+ {
+ MysqlConnection con;
+ for ( ;; ) {
+ con = pool.poll();
+ if ( con == null )
+ break;
+ if ( !con.isValid() ) {
+ con.release();
+ continue;
+ }
+ if ( !busyConnections.add( con ) )
+ throw new RuntimeException( "Tried to hand out a busy connection!" );
+ return con;
+ }
+ // No pooled connection
+ if ( busyConnections.size() > 20 ) {
+ LOGGER.warn( "Too many open MySQL connections. Possible connection leak!" );
+ return null;
+ }
+ try {
+ // Create fresh connection
+ String uri = "jdbc:mysql://" + host + "/" + dbname
+ + "?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8"
+ + "&characterSetResults=utf8&connectionCollation=utf8mb4_unicode_ci";
+ Connection rawConnection = DriverManager.getConnection( uri,
+ user, password );
+ // By convention in our program we don't want auto commit
+ rawConnection.setAutoCommit( false );
+ // Wrap into our proxy
+ con = new MysqlConnection( rawConnection );
+ // Keep track of busy mysql connection
+ if ( !busyConnections.add( con ) )
+ throw new RuntimeException( "Tried to hand out a busy connection!" );
+ return con;
+ } catch ( SQLException e ) {
+ LOGGER.info( "Failed to connect to local mysql server", e );
+ }
+ return null;
+ }
+
+ /**
+ * Called by a {@link MysqlConnection} when its <code>close()</code>-method
+ * is called, so the connection will be added to the pool of available
+ * connections again.
+ *
+ * @param connection
+ */
+ static void returnConnection( MysqlConnection connection )
+ {
+ if ( !busyConnections.remove( connection ) )
+ throw new RuntimeException( "Tried to return a mysql connection to the pool that was not taken!" );
+ pool.add( connection );
+ }
+
+ public static void printCharsetInformation()
+ {
+ LOGGER.info( "MySQL charset related variables:" );
+ try ( MysqlConnection connection = Database.getConnection() ) {
+ MysqlStatement stmt = connection.prepareStatement( "SHOW VARIABLES LIKE :what" );
+ stmt.setString( "what", "char%" );
+ ResultSet rs = stmt.executeQuery();
+ while ( rs.next() ) {
+ LOGGER.info( rs.getString( "Variable_name" ) + ": " + rs.getString( "Value" ) );
+ }
+ stmt.setString( "what", "collat%" );
+ rs = stmt.executeQuery();
+ while ( rs.next() ) {
+ LOGGER.info( rs.getString( "Variable_name" ) + ": " + rs.getString( "Value" ) );
+ }
+ } catch ( SQLException e ) {
+ LOGGER.error( "Query failed in Database.printCharsetInformation()", e );
+ }
+ LOGGER.info( "End of variables" );
+ }
+
+ public static void printDebug()
+ {
+ LOGGER.info( "Available: " + pool.size() );
+ LOGGER.info( "Busy: " + busyConnections.size() );
+ }
+
+}// end class
diff --git a/src/main/java/org/openslx/imagemaster/db/DbSatellite.java b/src/main/java/org/openslx/imagemaster/db/DbSatellite.java
deleted file mode 100644
index 4f5a0c8..0000000
--- a/src/main/java/org/openslx/imagemaster/db/DbSatellite.java
+++ /dev/null
@@ -1,156 +0,0 @@
-package org.openslx.imagemaster.db;
-
-import java.math.BigInteger;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-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.thrift.iface.OrganizationData;
-
-/**
- * Represents a satellite in the database.
- * Is used to authenticate the satellite.
- */
-public class DbSatellite
-{
- private static final Logger LOG = Logger.getLogger( DbSatellite.class );
-
- private String organizationId, address, name, publickeyString, authMethod;
- private PublicKey publickey = null;
- private List<String> suffixList = null;
-
- public DbSatellite( String organizationId, String address, String name, String authMethod, String publickeyString )
- {
- this.organizationId = organizationId;
- this.address = address;
- this.name = name;
- this.publickeyString = publickeyString;
- this.authMethod = authMethod;
- }
-
- public static DbSatellite fromOrganizationId( String organizationId )
- {
- return MySQL
- .findUniqueOrNull(
- DbSatellite.class,
- "SELECT satellite.organizationid, satellite.address, satellite.name, satellite.authmethod, satellite.publickey FROM satellite WHERE satellite.organizationid = ? LIMIT 1",
- organizationId );
- }
-
- public static DbSatellite fromSuffix( String suffix )
- {
- return MySQL
- .findUniqueOrNull(
- DbSatellite.class,
- "SELECT satellite.organizationid, satellite.address, satellite.name, satellite.authmethod, satellite.publickey FROM satellite"
- + " INNER JOIN satellite_suffix USING (organizationid)"
- + " WHERE satellite_suffix.suffix = ? LIMIT 1",
- suffix );
- }
-
- /**
- * Return all known satellites/organizations as List of {@link OrganizationData}, which can be
- * used directly by the thrift API.
- *
- * @return list of all known organizations/satellites
- */
- public static List<OrganizationData> asOrganizationDataList()
- {
- List<DbSatellite> sats = MySQL.findAll(
- DbSatellite.class,
- "SELECT satellite.organizationid, satellite.address, satellite.name, satellite.authmethod, satellite.publickey FROM satellite" );
- List<OrganizationData> orgData = new ArrayList<>();
- for ( DbSatellite sat : sats ) {
- orgData.add( new OrganizationData( sat.getOrganizationId(), sat.getName(), sat.getAuthenticationMethod(), sat.getSuffixList() ) );
- }
- return orgData;
- }
-
- public static DbSatellite fromPrefix( String prefix )
- {
- return null;
- }
-
- /*
- * Member methods
- */
-
- public String getAddress()
- {
- return address;
- }
-
- public String getName()
- {
- return name;
- }
-
- public String getOrganizationId()
- {
- return organizationId;
- }
-
- public String getAuthenticationMethod()
- {
- return authMethod;
- }
-
- public List<String> getSuffixList()
- {
- if ( this.suffixList == null )
- this.suffixList = DbSatelliteSuffix.forSatellite( this );
- return this.suffixList;
- }
-
- /**
- * Get the public key of this organization, if known and valid.
- *
- * @return Public key, null on error or not known
- */
- public PublicKey getPubkey()
- {
- if ( publickey == null && publickeyString != null ) {
- String parts[] = publickeyString.split( " " );
- BigInteger mod = null;
- BigInteger exp = null;
- for ( int i = 0; i < parts.length; ++i ) {
- if ( parts[i].startsWith( "mod:" ) ) {
- // modulus found.
- mod = new BigInteger( parts[i].substring( 4 ) );
- }
- if ( parts[i].startsWith( "exp:" ) ) {
- // exponent found.
- exp = new BigInteger( parts[i].substring( 4 ) );
- }
- }
- if ( mod == null ) {
- LOG.error( "No modulus for building public key was found." );
- return null;
- }
- if ( exp == null ) {
- LOG.error( "No public exponent for building public key was found." );
- return null;
- }
- try {
- publickey = new AsymKeyHolder( null, exp, mod ).getPublicKey();
- } catch ( InvalidKeySpecException | NoSuchAlgorithmException e ) {
- LOG.info( "PubKey of " + this.name + " is not valid.", e );
- } catch ( NumberFormatException e ) {
- LOG.info( "PubKey of " + this.name + " is corrupted in database!", e );
- }
- }
- return publickey;
- }
-
- public void updateAddress( String address, String organization )
- {
- this.address = address;
- this.organizationId = organization;
- MySQL.update( "UPDATE satellite SET address = ? WHERE organizationid = ?", address, organization );
- }
-
-}
diff --git a/src/main/java/org/openslx/imagemaster/db/DbSatelliteSuffix.java b/src/main/java/org/openslx/imagemaster/db/DbSatelliteSuffix.java
deleted file mode 100644
index a22d694..0000000
--- a/src/main/java/org/openslx/imagemaster/db/DbSatelliteSuffix.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.openslx.imagemaster.db;
-
-import java.util.List;
-
-public class DbSatelliteSuffix
-{
-
- public static List<String> forOrganization( String organizationId )
- {
- return MySQL.findAll( String.class, "SELECT satellite_suffix.suffix FROM satellite_suffix WHERE organizationid = ?", organizationId );
- }
-
- public static List<String> forSatellite( DbSatellite satellite )
- {
- return forOrganization( satellite.getOrganizationId() );
- }
-
-}
diff --git a/src/main/java/org/openslx/imagemaster/db/DbUser.java b/src/main/java/org/openslx/imagemaster/db/DbUser.java
deleted file mode 100644
index 87febe1..0000000
--- a/src/main/java/org/openslx/imagemaster/db/DbUser.java
+++ /dev/null
@@ -1,107 +0,0 @@
-package org.openslx.imagemaster.db;
-
-import java.util.List;
-
-import org.apache.log4j.Logger;
-import org.openslx.imagemaster.session.User;
-import org.openslx.imagemaster.thrift.iface.UserInfo;
-import org.openslx.imagemaster.util.Sha512Crypt;
-
-/**
- * Represents a user that can login against the masterserver.
- */
-public class DbUser extends User
-{
-
- private static Logger log = Logger.getLogger( DbUser.class );
-
- public DbUser( int userId, String login, String password, String organizationId,
- String firstName, String lastName, String eMail,
- String satelliteAddress )
- {
- super( userId, login, password, organizationId, firstName, lastName, eMail,
- satelliteAddress );
- }
-
- /**
- * Query database for user with given login
- *
- * @param login
- * (user@organizationSuffix)
- * @return instance of DbUser for matching entry from DB, or null if not
- * found
- */
- public static DbUser forLogin( final String login )
- {
- return MySQL
- .findUniqueOrNull(
- DbUser.class,
- "SELECT user.userid, user.login, user.password, user.organizationid, user.firstname, user.lastname, user.email, satellite.address FROM user"
- + " LEFT JOIN satellite USING (organizationid)"
- + " WHERE user.login = ? LIMIT 1",
- login );
- }
-
- /**
- * Query database for user with given userId
- *
- * @param userid
- * @return instance of DbUser for matching entry from DB, or null if not
- * found
- */
- public static DbUser forLogin( final int userid )
- {
- return MySQL
- .findUniqueOrNull(
- DbUser.class,
- "SELECT user.userid, user.login, user.password, user.organizationid, user.firstname, user.lastname, user.email, satellite.address FROM user"
- + " LEFT JOIN satellite USING (organizationid)"
- + " WHERE user.userid = ? LIMIT 1",
- userid );
- }
-
- public static boolean exists( final String login )
- {
- return forLogin( login ) != null;
- }
-
- public static DbUser forLogin( String login, String password )
- {
- DbUser user = forLogin( login );
- if ( user == null || !Sha512Crypt.verifyPassword( password, user.password ) )
- return null;
- return user;
- }
-
- public static boolean insertOrUpdate( User user )
- {
- log.debug( "Inserted user '" + user.login + "' into db." );
- MySQL.update(
- "INSERT INTO user (login, password, organizationid, firstname, lastname, email) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE password=VALUES(password), organizationid=VALUES(organizationid), firstname=VALUES(firstname), lastname=VALUES(lastname), email=VALUES(email)",
- user.login, user.password, user.organizationId, user.firstName, user.lastName, user.eMail );
- return false;
- }
-
- public static boolean insertOrUpdate(UserInfo userInfo) {
- log.debug( "Inserted user '" + userInfo.userId + "' into db." );
- int ret = MySQL.update(
- "INSERT INTO user (login, password, organizationid, firstname, lastname, email) VALUES (?, '', ?, ?, ?, ?) ON DUPLICATE KEY UPDATE organizationid=VALUES(organizationid), firstname=VALUES(firstname), lastname=VALUES(lastname), email=VALUES(email)",
- userInfo.userId, userInfo.organizationId, userInfo.firstName, userInfo.lastName, userInfo.eMail );
- return (ret != 0);
- }
-
- public static List<UserInfo> findUser( String organizationId, String searchTerm )
- {
- final String str = "%" + searchTerm + "%"; // TODO: Better handling, escape LIKE chars, or even make this use REGEXP
- if ( organizationId == null )
- return MySQL.findAll( UserInfo.class, "SELECT login, firstname, lastname, email, organizationid"
- + " FROM user"
- + " WHERE login LIKE ? OR firstname LIKE ? OR lastname LIKE ? OR email LIKE ?"
- + " LIMIT 100", str, str, str );
- return MySQL.findAll( UserInfo.class, "SELECT login, firstname, lastname, email, organizationid"
- + " FROM user"
- + " WHERE organizationid = ? AND (login LIKE ? OR firstname LIKE ? OR lastname LIKE ? OR email LIKE ?)"
- + " LIMIT 100", organizationId, str, str, str );
- }
-
-}
diff --git a/src/main/java/org/openslx/imagemaster/db/LdapUser.java b/src/main/java/org/openslx/imagemaster/db/LdapUser.java
deleted file mode 100644
index 988f43a..0000000
--- a/src/main/java/org/openslx/imagemaster/db/LdapUser.java
+++ /dev/null
@@ -1,168 +0,0 @@
-package org.openslx.imagemaster.db;
-
-import java.io.File;
-import java.io.IOException;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.cert.CertificateException;
-
-import javax.net.ssl.TrustManagerFactory;
-
-import org.apache.directory.api.ldap.model.cursor.CursorException;
-import org.apache.directory.api.ldap.model.cursor.EntryCursor;
-import org.apache.directory.api.ldap.model.entry.Entry;
-import org.apache.directory.api.ldap.model.exception.LdapException;
-import org.apache.directory.api.ldap.model.message.SearchScope;
-import org.apache.directory.ldap.client.api.LdapConnection;
-import org.apache.directory.ldap.client.api.LdapConnectionConfig;
-import org.apache.directory.ldap.client.api.LdapNetworkConnection;
-import org.apache.log4j.Logger;
-import org.apache.mina.filter.ssl.KeyStoreFactory;
-import org.openslx.imagemaster.Globals;
-import org.openslx.imagemaster.session.User;
-import org.openslx.imagemaster.thrift.iface.AuthenticationError;
-import org.openslx.imagemaster.thrift.iface.AuthenticationException;
-import org.openslx.imagemaster.util.Sha512Crypt;
-
-/**
- * Represents a user instance that was queries (primarily) from LDAP.
- * Additional information that is not provided by the LDAP server might
- * be fetched from other sources, like the local database (DbUser.java).
- */
-public class LdapUser extends User
-{
-
- private static final Logger log = Logger.getLogger( LdapUser.class );
-
- protected LdapUser(int userId, String username, String password, String organization, String firstName, String lastName, String eMail, String satelliteAddress)
- {
- super( userId, username, password, organization, firstName, lastName, eMail,
- satelliteAddress );
- }
-
- /**
- * Query LDAP for user with given login
- *
- * @param login Login of user in the form "prefix_username"
- * @return instance of LDAPUser for matching entry from LDAP or null if sth went wrong
- */
- public static LdapUser forLogin( final String login, final String password ) throws AuthenticationException
- {
- String username, organization, firstName, lastName, eMail, satelliteAddress = "";
-
- final String[] split = login.split( "_" );
- if (split.length != 2)
- throw new AuthenticationException(AuthenticationError.GENERIC_ERROR, "Login must be in form: prefix_username");
-
- LdapConnection connection = null;
- try {
- LdapConnectionConfig config = new LdapConnectionConfig();
-
- String ldapHost = Globals.getLdapHost();
- log.debug( "Setting host... " + ldapHost );
- config.setLdapHost( ldapHost );
-
- boolean useSsl = Globals.getLdapSsl();
- log.debug( "Setting use ssl... " + useSsl);
- config.setUseSsl( useSsl );
-
- int ldapPort = Globals.getLdapPort();
- log.debug( "Setting port... " + ldapPort );
- config.setLdapPort( ldapPort );
-
- // load keystore ...
- KeyStoreFactory ksf = new KeyStoreFactory();
- ksf.setDataFile( new File(Globals.getLdapKeystorePath()) );
- ksf.setPassword( Globals.getLdapKeystorePassword());
- ksf.setType( "jks" );
-
- // ... and set TrustManager
- TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() );
- tmf.init( ksf.newInstance() );
-
- config.setTrustManagers( tmf.getTrustManagers() );
-
- connection = new LdapNetworkConnection( config );
-
- log.debug( "Trying to bind..." );
- String bind = Globals.getLdapBindQuery().replace( "%", login );
- connection.bind( bind, password );
- //connection.bind();
- log.debug( "Bind successful" );
-
-
- // make search query
- EntryCursor cursor = connection.search( Globals.getLdapSearchBaseDn(),
- Globals.getLdapSearchFilter().replace( "%", login ), SearchScope.SUBTREE );
- // only use the first result
- cursor.next();
- Entry entry = cursor.get();
- username = entry.get( "cn" ).toString().split( " " )[1].split( "@" )[0];
- organization = entry.get( "cn" ).toString().split( "@" )[1];
- firstName = entry.get( "givenName" ).getString();
- lastName = entry.get( "sn" ).getString();
- eMail = entry.get( "mail" ).getString();
-
- // get the satellite address from db
- DbSatellite dbSatellite = DbSatellite.fromSuffix( organization );
- if ( dbSatellite != null ) {
- satelliteAddress = dbSatellite.getAddress();
- } else {
- // Organization is not known. This should not happen because the login would have failed then.
- throw new AuthenticationException( AuthenticationError.GENERIC_ERROR, "Your Organization is not known by the server. Please contact your administrator." );
- }
- // everything went fine
- return new LdapUser( 0, username, Sha512Crypt.Sha512_crypt( password, null, 0 ), organization, firstName, lastName, eMail, satelliteAddress );
- } catch ( LdapException e) {
- if ( e.getMessage().contains( "Cannot connect on the server" ) ) {
- DbSatellite dbSatellite = DbSatellite.fromPrefix(split[0]);
- if (dbSatellite == null) throw new AuthenticationException(AuthenticationError.INVALID_CREDENTIALS, "Credentials invalid.");
- String lo = split[1] + "@" + dbSatellite.getOrganizationId();
- log.info( "LDAP server could not be reached. Trying to connect locally with: " + lo );
- return LdapUser.localLogin(lo, password);
- }
- e.printStackTrace();
- throw new AuthenticationException( AuthenticationError.GENERIC_ERROR, "Something went wrong." );
- } catch ( CursorException e ) {
- e.printStackTrace();
- throw new AuthenticationException( AuthenticationError.INVALID_CREDENTIALS, "Could not find user entry." );
- } catch ( IOException e ) {
- // could not load keyfile
- e.printStackTrace();
- } catch ( NoSuchAlgorithmException e ) {
- // could not load algorithm
- e.printStackTrace();
- } catch ( KeyStoreException | NoSuchProviderException | CertificateException e ) {
- // some problem with the key
- e.printStackTrace();
- } finally {
- // close connection
- try {
- connection.unBind();
- connection.close();
- } catch ( IOException | LdapException e ) {
- // was not connected so don't do anything...
- }
- }
- return null;
- }
-
- /**
- * Login user locally if external Ldap server is not available
- * @param login Must be in form "username@organization"
- * @param password The user's password
- */
- private static LdapUser localLogin( String login, String password )
- {
- DbUser user = DbUser.forLogin( login );
- if (user == null) return null; // no user found
-
- // check users password
- if (!Sha512Crypt.verifyPassword( password, user.password )) return null;
-
- // return ldapuser if valid
- return new LdapUser( user.userId, user.login, Sha512Crypt.Sha512_crypt( password, null, 0 ), user.organizationId, user.firstName,
- user.lastName, user.eMail, user.satelliteAddress );
- }
-}
diff --git a/src/main/java/org/openslx/imagemaster/db/MySQL.java b/src/main/java/org/openslx/imagemaster/db/MySQL.java
deleted file mode 100644
index 9443879..0000000
--- a/src/main/java/org/openslx/imagemaster/db/MySQL.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package org.openslx.imagemaster.db;
-
-import java.io.FileInputStream;
-import java.io.BufferedInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.List;
-import java.util.Properties;
-
-import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
-
-import org.apache.log4j.Logger;
-import org.openslx.imagemaster.util.Util;
-
-import fi.evident.dalesbred.Database;
-
-/**
- * Class for talking to the DB via the dalesbred jdbc wrapper. Package private,
- * so only the Db* classes can actually communicate with the database.
- *
- */
-class MySQL
-{
-
- private static final Logger log = Logger.getLogger( MySQL.class );
- private static Database db = null;
-
- /**
- * Static initializer for setting up the database connection.
- * This gets called implicitly as soon as the class loader loads
- * the class. In most cases that happens when the class is being
- * accessed for the first time during run time.
- */
- static
- {
- // Load connection info from class (TODO: Make pretty)
- Properties properties = new Properties();
- try {
- final BufferedInputStream stream = new BufferedInputStream( new FileInputStream( "config/mysql.properties" ) );
- properties.load( stream );
- stream.close();
- } catch ( FileNotFoundException e ) {
- log.fatal( "config/mysql.properties not found!" );
- System.exit( 1 );
- } catch ( IOException e ) {
- log.fatal( "Error reading from config/mysql.properties: " + e.getMessage() );
- System.exit( 1 );
- } catch ( Exception e ) {
- log.fatal( "Generic error loading mysql properties file." );
- e.printStackTrace();
- System.exit( 1 );
- }
- final String host = properties.getProperty( "host" );
- final String dbname = properties.getProperty( "db" );
- final String user = properties.getProperty( "user" );
- final String password = properties.getProperty( "password" );
-
- Util.notNullFatal( host, "host not set in mysql properties" );
- Util.notNullFatal( dbname, "db not set in mysql properties" );
- Util.notNullFatal( user, "user not set in mysql properties" );
- Util.notNullFatal( password, "password not set in mysql properties" );
-
- // Setup db connection
- try {
- MysqlDataSource ds = new MysqlDataSource();
- ds.setServerName( host );
- ds.setDatabaseName( dbname );
- ds.setUser( user );
- ds.setPassword( password );
- ds.setEncoding( "UTF-8" );
- db = Database.forDataSource( ds );
- } catch ( Exception e ) {
- log.fatal( "Error initializing mysql data source!" );
- e.printStackTrace();
- System.exit( 1 );
- }
- }
-
- /**
- * Get a list of objects of the given class from the database.
- * The class needs a matching constructor for the query you pass in, i.e. number of
- * arguments has to be equal to number of columns returned by query.
- *
- * @param clazz The class to instanciate for the result(s)
- * @param sql The sql query to run
- * @param args Any number of arguments to the query (using the '?' placeholder)
- * @return A list containing the rows returned by the query, represented by the given class
- */
- protected static <T> List<T> findAll( final Class<T> clazz, final String sql, final Object... args )
- {
- return db.findAll( clazz, sql, args );
- }
-
- /**
- * Run a query on the database that will return at most one result.
- * If the query returns a row, it will be used to instanciate the given class. If
- * it doesn't return a row, null will be returned.
- *
- * @param clazz The class to instanciate for the result (if any)
- * @param sql The sql query to run
- * @param args Any number of arguments to the query (using the '?' placeholder)
- * @return Instance of clazz or null
- */
- protected static <T> T findUniqueOrNull( final Class<T> clazz, final String sql, final Object... args )
- {
- return db.findUniqueOrNull( clazz, sql, args );
- }
-
- /**
- * Run an update on the database, return number of rows affected.
- *
- * @param sql The update/insert query to run
- * @param args Any number of arguments to the query (using the '?' placeholder)
- * @return Number of rows affected by query
- */
- protected static int update( String sql, Object... args )
- {
- return db.update( sql, args );
- }
-}
diff --git a/src/main/java/org/openslx/imagemaster/db/MysqlConnection.java b/src/main/java/org/openslx/imagemaster/db/MysqlConnection.java
new file mode 100644
index 0000000..dcfa713
--- /dev/null
+++ b/src/main/java/org/openslx/imagemaster/db/MysqlConnection.java
@@ -0,0 +1,78 @@
+package org.openslx.imagemaster.db;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
+public class MysqlConnection implements AutoCloseable {
+
+ private static final Logger LOGGER = Logger.getLogger(MysqlConnection.class);
+
+ private static final int CONNECTION_TIMEOUT_MS = 5 * 60 * 1000;
+
+ private final long deadline = System.currentTimeMillis() + CONNECTION_TIMEOUT_MS;
+
+ private final Connection rawConnection;
+
+ private boolean hasPendingQueries = false;
+
+ private List<MysqlStatement> openStatements = new ArrayList<>();
+
+ MysqlConnection(Connection rawConnection) {
+ this.rawConnection = rawConnection;
+ }
+
+ public MysqlStatement prepareStatement(String sql) throws SQLException {
+ if (!sql.startsWith("SELECT"))
+ hasPendingQueries = true;
+ MysqlStatement statement = new MysqlStatement(rawConnection, sql);
+ openStatements.add(statement);
+ return statement;
+ }
+
+ public void commit() throws SQLException {
+ rawConnection.commit();
+ hasPendingQueries = false;
+ }
+
+ public void rollback() throws SQLException {
+ rawConnection.rollback();
+ hasPendingQueries = false;
+ }
+
+ boolean isValid() {
+ return System.currentTimeMillis() < deadline;
+ }
+
+ @Override
+ public void close() {
+ if (hasPendingQueries) {
+ LOGGER.warn("Mysql connection had uncommited queries on .close()");
+ hasPendingQueries = false;
+ }
+ try {
+ rawConnection.rollback();
+ } catch (SQLException e) {
+ LOGGER.warn("Rolling back uncommited queries failed!", e);
+ }
+ if (!openStatements.isEmpty()) {
+ for (MysqlStatement statement : openStatements) {
+ statement.close();
+ }
+ openStatements.clear();
+ }
+ Database.returnConnection(this);
+ }
+
+ void release() {
+ try {
+ rawConnection.close();
+ } catch (SQLException e) {
+ // Nothing meaningful to do
+ }
+ }
+
+}
diff --git a/src/main/java/org/openslx/imagemaster/db/MysqlStatement.java b/src/main/java/org/openslx/imagemaster/db/MysqlStatement.java
new file mode 100644
index 0000000..391aed0
--- /dev/null
+++ b/src/main/java/org/openslx/imagemaster/db/MysqlStatement.java
@@ -0,0 +1,321 @@
+package org.openslx.imagemaster.db;
+
+import java.io.Closeable;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class for creating {@link PreparedStatement}s with named parameters. Based on
+ * <a href=
+ * "http://www.javaworld.com/article/2077706/core-java/named-parameters-for-preparedstatement.html?page=2"
+ * >Named Parameters for PreparedStatement</a>
+ */
+public class MysqlStatement implements Closeable {
+
+ private static final QueryCache cache = new QueryCache();
+
+ private final PreparsedQuery query;
+
+ private final PreparedStatement statement;
+
+ private final List<ResultSet> openResultSets = new ArrayList<>();
+
+ MysqlStatement(Connection con, String sql) throws SQLException {
+ PreparsedQuery query;
+ synchronized (cache) {
+ query = cache.get(sql);
+ }
+ if (query == null) {
+ query = parse(sql);
+ synchronized (cache) {
+ cache.put(sql, query);
+ }
+ }
+ this.query = query;
+ this.statement = con.prepareStatement(query.sql);
+ }
+
+ /**
+ * Returns the indexes for a parameter.
+ *
+ * @param name parameter name
+ * @return parameter indexes
+ * @throws IllegalArgumentException if the parameter does not exist
+ */
+ private List<Integer> getIndexes(String name) {
+ List<Integer> indexes = query.indexMap.get(name);
+ if (indexes == null) {
+ throw new IllegalArgumentException("Parameter not found: " + name);
+ }
+ return indexes;
+ }
+
+ /**
+ * Sets a parameter.
+ *
+ * @param name parameter name
+ * @param value parameter value
+ * @throws SQLException if an error occurred
+ * @throws IllegalArgumentException if the parameter does not exist
+ * @see PreparedStatement#setObject(int, java.lang.Object)
+ */
+ public void setObject(String name, Object value) throws SQLException {
+ List<Integer> indexes = getIndexes(name);
+ for (Integer index : indexes) {
+ statement.setObject(index, value);
+ }
+ }
+
+ /**
+ * Sets a parameter.
+ *
+ * @param name parameter name
+ * @param value parameter value
+ * @throws SQLException if an error occurred
+ * @throws IllegalArgumentException if the parameter does not exist
+ * @see PreparedStatement#setString(int, java.lang.String)
+ */
+ public void setString(String name, String value) throws SQLException {
+ List<Integer> indexes = getIndexes(name);
+ for (Integer index : indexes) {
+ statement.setString(index, value);
+ }
+ }
+
+ /**
+ * Sets a parameter.
+ *
+ * @param name parameter name
+ * @param value parameter value
+ * @throws SQLException if an error occurred
+ * @throws IllegalArgumentException if the parameter does not exist
+ * @see PreparedStatement#setInt(int, int)
+ */
+ public void setInt(String name, int value) throws SQLException {
+ List<Integer> indexes = getIndexes(name);
+ for (Integer index : indexes) {
+ statement.setInt(index, value);
+ }
+ }
+
+ /**
+ * Sets a parameter.
+ *
+ * @param name parameter name
+ * @param value parameter value
+ * @throws SQLException if an error occurred
+ * @throws IllegalArgumentException if the parameter does not exist
+ * @see PreparedStatement#setLong(int, long)
+ */
+ public void setLong(String name, long value) throws SQLException {
+ List<Integer> indexes = getIndexes(name);
+ for (Integer index : indexes) {
+ statement.setLong(index, value);
+ }
+ }
+
+ /**
+ * Sets a parameter.
+ *
+ * @param name parameter name
+ * @param value parameter value
+ * @throws SQLException if an error occurred
+ * @throws IllegalArgumentException if the parameter does not exist
+ * @see PreparedStatement#setBoolean(int, boolean)
+ */
+ public void setBoolean(String name, boolean value) throws SQLException {
+ List<Integer> indexes = getIndexes(name);
+ for (Integer index : indexes) {
+ statement.setBoolean(index, value);
+ }
+ }
+
+ /**
+ * Sets a parameter.
+ *
+ * @param name parameter name
+ * @param value parameter value
+ * @throws SQLException if an error occurred
+ * @throws IllegalArgumentException if the parameter does not exist
+ * @see PreparedStatement#setBoolean(int, boolean)
+ */
+ public void setBinary(String name, byte[] value) throws SQLException {
+ List<Integer> indexes = getIndexes(name);
+ for (Integer index : indexes) {
+ statement.setBytes(index, value);
+ }
+ }
+
+ /**
+ * Executes the statement.
+ *
+ * @return true if the first result is a {@link ResultSet}
+ * @throws SQLException if an error occurred
+ * @see PreparedStatement#execute()
+ */
+ public boolean execute() throws SQLException {
+ return statement.execute();
+ }
+
+ /**
+ * Executes the statement, which must be a query.
+ *
+ * @return the query results
+ * @throws SQLException if an error occurred
+ * @see PreparedStatement#executeQuery()
+ */
+ public ResultSet executeQuery() throws SQLException {
+ ResultSet rs = statement.executeQuery();
+ openResultSets.add(rs);
+ return rs;
+ }
+
+ /**
+ * Executes the statement, which must be an SQL INSERT, UPDATE or DELETE
+ * statement; or an SQL statement that returns nothing, such as a DDL
+ * statement.
+ *
+ * @return number of rows affected
+ * @throws SQLException if an error occurred
+ * @see PreparedStatement#executeUpdate()
+ */
+ public int executeUpdate() throws SQLException {
+ return statement.executeUpdate();
+ }
+
+ /**
+ * Closes the statement.
+ *
+ * @see Statement#close()
+ */
+ @Override
+ public void close() {
+ for (ResultSet rs : openResultSets) {
+ try {
+ rs.close();
+ } catch (SQLException e) {
+ //
+ }
+ }
+ try {
+ statement.close();
+ } catch (SQLException e) {
+ // Nothing to do
+ }
+ }
+
+ /**
+ * Adds the current set of parameters as a batch entry.
+ *
+ * @throws SQLException if something went wrong
+ */
+ public void addBatch() throws SQLException {
+ statement.addBatch();
+ }
+
+ /**
+ * Executes all of the batched statements.
+ *
+ * See {@link Statement#executeBatch()} for details.
+ *
+ * @return update counts for each statement
+ * @throws SQLException if something went wrong
+ */
+ public int[] executeBatch() throws SQLException {
+ return statement.executeBatch();
+ }
+
+ // static methods
+
+ private static PreparsedQuery parse(String query) {
+ int length = query.length();
+ StringBuffer parsedQuery = new StringBuffer(length);
+ Map<String, List<Integer>> paramMap = new HashMap<>();
+ boolean inSingleQuote = false;
+ boolean inDoubleQuote = false;
+ boolean hasBackslash = false;
+ int index = 1;
+
+ for (int i = 0; i < length; i++) {
+ char c = query.charAt(i);
+ if (hasBackslash) {
+ // Last char was a backslash, so we ignore the current char
+ hasBackslash = false;
+ } else if (c == '\\') {
+ // This is a backslash, next char will be escaped
+ hasBackslash = true;
+ } else if (inSingleQuote) {
+ // End of quoted string
+ if (c == '\'') {
+ inSingleQuote = false;
+ }
+ } else if (inDoubleQuote) {
+ // End of quoted string
+ if (c == '"') {
+ inDoubleQuote = false;
+ }
+ } else {
+ // Not in string, look for named params
+ if (c == '\'') {
+ inSingleQuote = true;
+ } else if (c == '"') {
+ inDoubleQuote = true;
+ } else if (c == ':' && i + 1 < length && Character.isJavaIdentifierStart(query.charAt(i + 1))) {
+ int j = i + 2;
+ while (j < length && Character.isJavaIdentifierPart(query.charAt(j))) {
+ j++;
+ }
+ String name = query.substring(i + 1, j);
+ c = '?'; // replace the parameter with a question mark
+ i += name.length(); // skip past the end of the parameter
+
+ List<Integer> indexList = paramMap.get(name);
+ if (indexList == null) {
+ indexList = new ArrayList<>();
+ paramMap.put(name, indexList);
+ }
+ indexList.add(new Integer(index));
+
+ index++;
+ }
+ }
+ parsedQuery.append(c);
+ }
+
+ return new PreparsedQuery(parsedQuery.toString(), paramMap);
+ }
+
+ // private helper classes
+
+ private static class PreparsedQuery {
+ private final Map<String, List<Integer>> indexMap;
+ private final String sql;
+
+ public PreparsedQuery(String sql, Map<String, List<Integer>> indexMap) {
+ this.sql = sql;
+ this.indexMap = indexMap;
+ }
+ }
+
+ private static class QueryCache extends LinkedHashMap<String, PreparsedQuery> {
+ private static final long serialVersionUID = 1L;
+
+ public QueryCache() {
+ super(30, (float) 0.75, true);
+ }
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<String, PreparsedQuery> eldest) {
+ return size() > 40;
+ }
+ }
+
+}
diff --git a/src/main/java/org/openslx/imagemaster/db/Paginator.java b/src/main/java/org/openslx/imagemaster/db/Paginator.java
new file mode 100644
index 0000000..267a1a3
--- /dev/null
+++ b/src/main/java/org/openslx/imagemaster/db/Paginator.java
@@ -0,0 +1,11 @@
+package org.openslx.imagemaster.db;
+
+public class Paginator {
+
+ public static final int PER_PAGE = 200;
+
+ public static String limitStatement(int page) {
+ return " LIMIT " + (page * PER_PAGE) + ", " + PER_PAGE;
+ }
+
+}
diff --git a/src/main/java/org/openslx/imagemaster/db/DbImage.java b/src/main/java/org/openslx/imagemaster/db/mappers/DbImage.java
index bc4fa25..027ddf6 100644
--- a/src/main/java/org/openslx/imagemaster/db/DbImage.java
+++ b/src/main/java/org/openslx/imagemaster/db/mappers/DbImage.java
@@ -1,10 +1,10 @@
-package org.openslx.imagemaster.db;
+package org.openslx.imagemaster.db.mappers;
import java.util.List;
+import org.openslx.bwlp.thrift.iface.ImagePublishData;
import org.openslx.imagemaster.Globals;
import org.openslx.imagemaster.serverconnection.UploadingImage;
-import org.openslx.imagemaster.thrift.iface.ImageData;
import org.openslx.imagemaster.util.Util;
/**
@@ -95,11 +95,13 @@ public class DbImage
/**
* Insert a new image into database
*
- * @param imageData The metadata of the image
- * @param filepath Local storage path of image
+ * @param imageData
+ * The metadata of the image
+ * @param filepath
+ * Local storage path of image
* @return Affected rows
*/
- public static int insert( ImageData imageData, String filepath )
+ public static int insert( ImagePublishData imageData, String filepath )
{
int numBlocks = Util.getNumberOfBlocks( imageData.fileSize, Globals.blockSize );
String missingBlocksList = "";
@@ -213,7 +215,8 @@ public class DbImage
DbUser user = DbUser.forLogin( this.ownerId );
if ( user != null )
owner = user.getLogin();
- return new ImageData( this.uuid, this.revision, this.title, this.createTime,
+ return new ImageData(
+ this.uuid, this.revision, this.title, this.createTime,
this.updateTime, owner, this.operatingSystem, this.isValid,
this.isDeleted, this.longDescription, this.fileSize );
}
diff --git a/src/main/java/org/openslx/imagemaster/db/mappers/DbOperatingSystem.java b/src/main/java/org/openslx/imagemaster/db/mappers/DbOperatingSystem.java
new file mode 100644
index 0000000..1504e50
--- /dev/null
+++ b/src/main/java/org/openslx/imagemaster/db/mappers/DbOperatingSystem.java
@@ -0,0 +1,36 @@
+package org.openslx.imagemaster.db.mappers;
+
+import java.util.List;
+
+import org.openslx.bwlp.thrift.iface.OperatingSystem;
+import org.openslx.imagemaster.db.MySQL;
+import org.openslx.util.TimeoutReference;
+
+public class DbOperatingSystem
+{
+
+ private static TimeoutReference<List<OperatingSystem>> cached = new TimeoutReference<List<OperatingSystem>>(
+ 30000, null );
+
+ private DbOperatingSystem()
+ {
+ }
+
+ public static List<OperatingSystem> getAll()
+ {
+ List<OperatingSystem> list = cached.get();
+ if ( list != null )
+ return list;
+ list = MySQL.findAll(
+ OperatingSystem.class,
+ "SELECT osid, displayname, NULL, architecture"
+ + " FROM operatingsystem" );
+ for ( OperatingSystem os : list ) {
+ os.virtualizerOsId = MySQL.findMap( String.class, String.class,
+ "SELECT virtid, virtoskeyword FROM os_x_virt WHERE osid = ?", os.osId );
+ }
+ cached.set( list );
+ return list;
+ }
+
+}
diff --git a/src/main/java/org/openslx/imagemaster/db/mappers/DbOrganization.java b/src/main/java/org/openslx/imagemaster/db/mappers/DbOrganization.java
new file mode 100644
index 0000000..cf1a20a
--- /dev/null
+++ b/src/main/java/org/openslx/imagemaster/db/mappers/DbOrganization.java
@@ -0,0 +1,137 @@
+package org.openslx.imagemaster.db.mappers;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.openslx.bwlp.thrift.iface.Organization;
+import org.openslx.imagemaster.db.Database;
+import org.openslx.imagemaster.db.MysqlConnection;
+import org.openslx.imagemaster.db.MysqlStatement;
+
+/**
+ * Represents an organization in the database.
+ * Is used to authenticate the organization.
+ */
+public class DbOrganization
+{
+ private static final Logger LOGGER = Logger.getLogger( DbOrganization.class );
+
+ private static final String organizationBaseSql = "SELECT"
+ + " o.organizationid, o.name, o.authmethod, o.publickey"
+ + " FROM organization o";
+
+ private static final String suffixListFromOrgSql = "SELECT suffix FROM organization_suffix"
+ + " WHERE organizationid = :organizationid";
+
+ private static Organization fromResultSet( MysqlConnection connection, ResultSet rs ) throws SQLException
+ {
+ String organizationId = rs.getString( "organizationid" );
+ String ecpUrl = rs.getString( "authmethod" );
+ if ( ecpUrl != null && !ecpUrl.startsWith( "http" ) ) {
+ ecpUrl = null;
+ }
+ return new Organization( organizationId, rs.getString( "name" ), ecpUrl, getSuffixList( connection,
+ organizationId ) );
+ }
+
+ /**
+ * Get organization by id. Returns null if not found.
+ *
+ * @param organizationId
+ * @return
+ * @throws SQLException
+ */
+ public static Organization fromOrganizationId( String organizationId ) throws SQLException
+ {
+ try ( MysqlConnection connection = Database.getConnection() ) {
+ MysqlStatement stmt = connection.prepareStatement( organizationBaseSql + " WHERE o.organizationid = :organizationid" );
+ stmt.setString( "organizationid", organizationId );
+ ResultSet rs = stmt.executeQuery();
+ if ( !rs.next() )
+ return null;
+ return fromResultSet( connection, rs );
+ } catch ( SQLException e ) {
+ LOGGER.error( "Query failed in DbOrganization.fromOrganizationId()", e );
+ throw e;
+ }
+ }
+
+ public static Organization fromSuffix( String suffix ) throws SQLException
+ {
+ try ( MysqlConnection connection = Database.getConnection() ) {
+ MysqlStatement stmt = connection.prepareStatement( organizationBaseSql
+ + " INNER JOIN organization_suffix s USING (organizationid)"
+ + " WHERE s.suffix = :suffix" );
+ stmt.setString( "suffix", suffix );
+ ResultSet rs = stmt.executeQuery();
+ if ( !rs.next() )
+ return null;
+ return fromResultSet( connection, rs );
+ } catch ( SQLException e ) {
+ LOGGER.error( "Query failed in DbOrganization.fromSuffix()", e );
+ throw e;
+ }
+ }
+
+ private static List<String> suffixForOrg( MysqlStatement stmt, String organizationId ) throws SQLException
+ {
+ stmt.setString( "organizationid", organizationId );
+ ResultSet rs = stmt.executeQuery();
+ List<String> list = new ArrayList<>();
+ while ( rs.next() ) {
+ list.add( rs.getString( "suffix" ) );
+ }
+ return list;
+ }
+
+ /**
+ * Return all known satellites/organizations as List of {@link OrganizationData}, which can be
+ * used directly by the thrift API.
+ *
+ * @return list of all known organizations/satellites
+ * @throws SQLException
+ */
+ public static List<Organization> getAll() throws SQLException
+ {
+ try ( MysqlConnection connection = Database.getConnection() ) {
+ MysqlStatement stmt = connection.prepareStatement( organizationBaseSql );
+ ResultSet rsOrg = stmt.executeQuery();
+ MysqlStatement stmtSuffix = connection.prepareStatement( suffixListFromOrgSql );
+ List<Organization> list = new ArrayList<>();
+ while ( rsOrg.next() ) {
+ String organizationId = rsOrg.getString( "organizationid" );
+ String ecpUrl = rsOrg.getString( "authmethod" );
+ if ( ecpUrl != null && !ecpUrl.startsWith( "http" ) ) {
+ ecpUrl = null;
+ }
+ List<String> suffixList = suffixForOrg( stmtSuffix, organizationId );
+ list.add( new Organization( organizationId, rsOrg.getString( "name" ), ecpUrl, suffixList ) );
+ }
+ return list;
+ } catch ( SQLException e ) {
+ LOGGER.error( "Query failed in DbOrganization.getAll()", e );
+ throw e;
+ }
+ }
+
+ public static DbOrganization fromPrefix( String prefix )
+ {
+ return null;
+ }
+
+ public static List<String> getSuffixList( MysqlConnection connection, String organizationId ) throws SQLException
+ {
+ List<String> list = new ArrayList<>();
+ MysqlStatement stmt = connection.prepareStatement( suffixListFromOrgSql );
+ stmt.setString( "organizationid", organizationId );
+ ResultSet rs = stmt.executeQuery();
+ while ( rs.next() ) {
+ list.add( rs.getString( "suffix" ) );
+ }
+ return list;
+ }
+
+}
diff --git a/src/main/java/org/openslx/imagemaster/db/DbPendingSatellite.java b/src/main/java/org/openslx/imagemaster/db/mappers/DbPendingSatellite.java
index 30ec5a2..15cdbb9 100644
--- a/src/main/java/org/openslx/imagemaster/db/DbPendingSatellite.java
+++ b/src/main/java/org/openslx/imagemaster/db/mappers/DbPendingSatellite.java
@@ -1,6 +1,7 @@
-package org.openslx.imagemaster.db;
+package org.openslx.imagemaster.db.mappers;
import org.apache.log4j.Logger;
+import org.openslx.imagemaster.db.MySQL;
public class DbPendingSatellite
{
diff --git a/src/main/java/org/openslx/imagemaster/db/mappers/DbUser.java b/src/main/java/org/openslx/imagemaster/db/mappers/DbUser.java
new file mode 100644
index 0000000..ed55d8a
--- /dev/null
+++ b/src/main/java/org/openslx/imagemaster/db/mappers/DbUser.java
@@ -0,0 +1,108 @@
+package org.openslx.imagemaster.db.mappers;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.openslx.bwlp.thrift.iface.Role;
+import org.openslx.bwlp.thrift.iface.TNotFoundException;
+import org.openslx.bwlp.thrift.iface.UserInfo;
+import org.openslx.imagemaster.db.Database;
+import org.openslx.imagemaster.db.MysqlConnection;
+import org.openslx.imagemaster.db.MysqlStatement;
+import org.openslx.imagemaster.db.models.LocalUser;
+import org.openslx.imagemaster.util.Sha512Crypt;
+
+/**
+ * Represents a user that can login against the masterserver.
+ */
+public class DbUser
+{
+
+ private static final Logger LOGGER = Logger.getLogger( DbUser.class );
+
+ private static final String localUserSql = "SELECT"
+ + " user.userid, user.login, user.password, user.organizationid, user.firstname, user.lastname, user.email,"
+ + " organization.address FROM user"
+ + " LEFT JOIN organization USING (organizationid)";
+
+ private static LocalUser localFromRs( ResultSet rs ) throws SQLException
+ {
+ return new LocalUser( rs.getInt( "userid" ), rs.getString( "login" ), rs.getString( "password" ),
+ rs.getString( "organizationid" ), rs.getString( "firstname" ), rs.getString( "lastname" ), rs.getString( "email" ),
+ Role.TUTOR );
+ }
+
+ /**
+ * Query database for user with given login
+ *
+ * @param login (global user-id, login@org for test-accounts)
+ * @return instance of DbUser for matching entry from DB, or null if not
+ * found
+ * @throws SQLException if the query fails
+ */
+ public static LocalUser forLogin( final String login ) throws SQLException
+ {
+ try ( MysqlConnection connection = Database.getConnection() ) {
+ MysqlStatement stmt = connection.prepareStatement( localUserSql
+ + " WHERE user.login = :login" );
+ stmt.setString( "login", login );
+ ResultSet rs = stmt.executeQuery();
+ if ( !rs.next() )
+ return null;
+ return localFromRs( rs );
+ } catch ( SQLException e ) {
+ LOGGER.error( "Query failed in DbUser.forLogin()", e );
+ throw e;
+ }
+ }
+
+ public static UserInfo getUserInfo( final String login ) throws SQLException, TNotFoundException
+ {
+ LocalUser user = forLogin( login );
+ if ( user == null )
+ throw new TNotFoundException();
+ return user.toUserInfo();
+ }
+
+ /**
+ * Query database for user with given userId
+ *
+ * @param userid
+ * @return instance of DbUser for matching entry from DB, or null if not
+ * found
+ * @throws SQLException
+ */
+ public static LocalUser forLogin( final int userId ) throws SQLException
+ {
+ try ( MysqlConnection connection = Database.getConnection() ) {
+ MysqlStatement stmt = connection.prepareStatement( localUserSql
+ + " WHERE user.userid = :userid" );
+ stmt.setInt( "userid", userId );
+ ResultSet rs = stmt.executeQuery();
+ if ( !rs.next() )
+ return null;
+ return localFromRs( rs );
+ } catch ( SQLException e ) {
+ LOGGER.error( "Query failed in DbUser.forLogin()", e );
+ throw e;
+ }
+ }
+
+ public static LocalUser forLogin( String login, String password ) throws SQLException
+ {
+ LocalUser user = forLogin( login );
+ if ( user == null || !Sha512Crypt.verifyPassword( password, user.password ) )
+ return null;
+ return user;
+ }
+
+ public static List<UserInfo> findUser( String organizationId, String searchTerm )
+ {
+ // TODO Implement
+ return new ArrayList<>( 0 );
+ }
+
+}
diff --git a/src/main/java/org/openslx/imagemaster/db/models/LocalOrganization.java b/src/main/java/org/openslx/imagemaster/db/models/LocalOrganization.java
new file mode 100644
index 0000000..4b5f076
--- /dev/null
+++ b/src/main/java/org/openslx/imagemaster/db/models/LocalOrganization.java
@@ -0,0 +1,62 @@
+package org.openslx.imagemaster.db.models;
+
+import java.math.BigInteger;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+
+import org.apache.log4j.Logger;
+import org.openslx.encryption.AsymKeyHolder;
+
+public class LocalOrganization
+{
+
+ private static final Logger LOGGER = Logger.getLogger( LocalOrganization.class );
+
+ private PublicKey publickey;
+
+ private String publickeyString;
+
+ private String name;
+
+ /**
+ * Get the public key of this organization, if known and valid.
+ *
+ * @return Public key, null on error or not known
+ */
+ public PublicKey getPubkey()
+ {
+ if ( publickey == null && publickeyString != null ) {
+ String parts[] = publickeyString.split( " " );
+ BigInteger mod = null;
+ BigInteger exp = null;
+ for ( int i = 0; i < parts.length; ++i ) {
+ if ( parts[i].startsWith( "mod:" ) ) {
+ // modulus found.
+ mod = new BigInteger( parts[i].substring( 4 ) );
+ }
+ if ( parts[i].startsWith( "exp:" ) ) {
+ // exponent found.
+ exp = new BigInteger( parts[i].substring( 4 ) );
+ }
+ }
+ if ( mod == null ) {
+ LOGGER.error( "No modulus for building public key was found." );
+ return null;
+ }
+ if ( exp == null ) {
+ LOGGER.error( "No public exponent for building public key was found." );
+ return null;
+ }
+ try {
+ publickey = new AsymKeyHolder( null, exp, mod ).getPublicKey();
+ } catch ( InvalidKeySpecException | NoSuchAlgorithmException e ) {
+ LOGGER.info( "PubKey of " + this.name + " is not valid.", e );
+ } catch ( NumberFormatException e ) {
+ LOGGER.info( "PubKey of " + this.name + " is corrupted in database!", e );
+ }
+ }
+ return publickey;
+ }
+
+}
diff --git a/src/main/java/org/openslx/imagemaster/session/User.java b/src/main/java/org/openslx/imagemaster/db/models/LocalUser.java
index a64ab5b..4e0d4fb 100644
--- a/src/main/java/org/openslx/imagemaster/session/User.java
+++ b/src/main/java/org/openslx/imagemaster/db/models/LocalUser.java
@@ -1,30 +1,33 @@
-package org.openslx.imagemaster.session;
+package org.openslx.imagemaster.db.models;
+
+import org.openslx.bwlp.thrift.iface.Role;
+import org.openslx.bwlp.thrift.iface.UserInfo;
/**
* Represents a user. Should be extended and given an according static method to
* instantiate by loading data from some backend.
*
*/
-public abstract class User
+public class LocalUser
{
public final String login, organizationId;
public final String password;
public final String firstName, lastName;
public final String eMail;
- public final String satelliteAddress;
+ public final Role role;
public final int userId;
- protected User( int userId, String login, String password, String organization, String firstName, String lastName, String eMail,
- String satelliteAddress )
+ public LocalUser( int userId, String login, String password, String organizationId, String firstName, String lastName, String eMail,
+ Role tutor )
{
this.userId = userId;
this.login = login;
- this.organizationId = organization;
+ this.organizationId = organizationId;
this.password = password;
this.firstName = firstName;
this.lastName = lastName;
this.eMail = eMail;
- this.satelliteAddress = satelliteAddress;
+ this.role = tutor;
}
@Override
@@ -39,13 +42,16 @@ public abstract class User
sb.append( this.lastName );
sb.append( ' ' );
sb.append( this.eMail );
- if ( this.satelliteAddress != null ) {
- sb.append( ' ' );
- sb.append( this.satelliteAddress );
- }
return sb.toString();
}
+ public UserInfo toUserInfo()
+ {
+ UserInfo ui = new UserInfo( login, firstName, lastName, eMail, organizationId );
+ ui.role = role;
+ return ui;
+ }
+
public String getLogin()
{
return login;
diff --git a/src/main/java/org/openslx/imagemaster/localrpc/JsonUser.java b/src/main/java/org/openslx/imagemaster/localrpc/JsonUser.java
new file mode 100644
index 0000000..8f13084
--- /dev/null
+++ b/src/main/java/org/openslx/imagemaster/localrpc/JsonUser.java
@@ -0,0 +1,23 @@
+package org.openslx.imagemaster.localrpc;
+
+import org.openslx.imagemaster.session.User;
+
+public class JsonUser
+{
+
+ private String login = null;
+ private String organizationid = null;
+ private String firstName = null;
+ private String lastName = null;
+ private String mail = null;
+ private String role = null;
+ private int userId = -1;
+
+ public User toUser()
+ {
+ if ( userId <= 0 || firstName == null || lastName == null || firstName.isEmpty() || lastName.isEmpty() )
+ return null;
+ return new ShibUser( userId, login, organizationid, firstName, lastName, mail, role );
+ }
+
+}
diff --git a/src/main/java/org/openslx/imagemaster/localrpc/NetworkHandler.java b/src/main/java/org/openslx/imagemaster/localrpc/NetworkHandler.java
new file mode 100644
index 0000000..96b1212
--- /dev/null
+++ b/src/main/java/org/openslx/imagemaster/localrpc/NetworkHandler.java
@@ -0,0 +1,188 @@
+package org.openslx.imagemaster.localrpc;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.apache.log4j.Logger;
+import org.openslx.bwlp.thrift.iface.ClientSessionData;
+import org.openslx.imagemaster.session.Session;
+import org.openslx.imagemaster.session.SessionManager;
+import org.openslx.imagemaster.session.User;
+
+import com.google.gson.Gson;
+
+/**
+ * The network listener that will receive incoming UDP packets, try to process
+ * them, and then send a reply.
+ */
+public class NetworkHandler implements Runnable
+{
+
+ private static final Logger log = Logger.getLogger( NetworkHandler.class );
+
+ private Thread sendThread = null;
+ /**
+ * Sender instance (Runnable handling outgoing packets)
+ */
+ private final Sender sender;
+ /**
+ * UDP socket for sending and receiving.
+ */
+ private final DatagramSocket socket;
+ /**
+ * Gson class
+ */
+ private final Gson gson = new Gson();
+
+ /**
+ * Initialize the NetworkHandler by starting threads and opening the socket.
+ */
+ public NetworkHandler( int port, InetAddress listenAddress ) throws SocketException
+ {
+ socket = new DatagramSocket( port, listenAddress );
+ sendThread = new Thread( sender = new Sender() );
+ }
+
+ public void shutdown()
+ {
+ socket.close();
+ }
+
+ /**
+ * Prepare and enqueue reply for client request.
+ * Only ever to be called from the receiving thread. The reply message is crafted
+ * and then handed over to the sending thread.
+ *
+ * @param destination SocketAddress of the client
+ * @param messageId The same ID the client used in it's request.
+ * It's echoed back to the client to enable request bursts, and has no meaning for the
+ * server.
+ * @param status A TaskStatus instance to be serialized to json and sent to the client.
+ */
+ private void send( SocketAddress destination, byte[] buffer )
+ {
+ final DatagramPacket packet;
+ try {
+ packet = new DatagramPacket( buffer, buffer.length, destination );
+ } catch ( SocketException e ) {
+ log.warn( "Could not construct datagram packet for target " + destination.toString() );
+ e.printStackTrace();
+ return;
+ }
+ sender.send( packet );
+ }
+
+ /**
+ * Main loop of receiving thread - wait until a packet arrives, then try to handle/decode
+ */
+ @Override
+ public void run()
+ {
+ byte readBuffer[] = new byte[ 66000 ];
+ try {
+ sendThread.start();
+ while ( !Thread.interrupted() ) {
+ DatagramPacket packet = new DatagramPacket( readBuffer, readBuffer.length );
+ try {
+ socket.receive( packet );
+ } catch ( IOException e ) {
+ log.info( "IOException on UDP socket when reading: " + e.getMessage() );
+ Thread.sleep( 100 );
+ continue;
+ }
+ if ( packet.getLength() < 2 ) {
+ log.debug( "Message too short" );
+ continue;
+ }
+ String payload = new String( readBuffer, 0, packet.getLength(), StandardCharsets.UTF_8 );
+ try {
+ String reply = handle( payload );
+ if ( reply != null )
+ send( packet.getSocketAddress(), reply.getBytes( StandardCharsets.UTF_8 ) );
+ } catch ( Throwable t ) {
+ log.error( "Exception in RequestParser: " + t.toString() );
+ log.error( "Payload was: " + payload );
+ t.printStackTrace();
+ }
+ }
+ } catch ( InterruptedException e ) {
+ Thread.currentThread().interrupt();
+ } finally {
+ sendThread.interrupt();
+ log.info( "UDP receiver finished." );
+ }
+ }
+
+ private String handle( String payload )
+ {
+ try {
+ JsonUser ju = gson.fromJson( payload, JsonUser.class );
+ User u = ju.toUser();
+ if ( u == null ) {
+ log.warn( "Invalid or inomplete RPC data (" + payload + ")" );
+ return "Invalid or incomplete RPC data";
+ }
+ ClientSessionData sd = SessionManager.addSession( new Session( u ) );
+ return "TOKEN:" + sd.authToken + " SESSIONID:" + sd.sessionId;
+ } catch ( Throwable t ) {
+ log.error( "Exception on json decode", t );
+ }
+ return "Json error";
+ }
+
+ /**
+ * Private sending thread.
+ * Use blocking queue, wait for packet to be added to it, then try to send.
+ */
+ private class Sender implements Runnable
+ {
+
+ /**
+ * Queue to stuff outgoing packets into.
+ */
+ private final BlockingQueue<DatagramPacket> queue = new LinkedBlockingQueue<>( 128 );
+
+ /**
+ * Wait until something is put into the queue, then send it.
+ */
+ @Override
+ public void run()
+ {
+ try {
+ while ( !Thread.interrupted() ) {
+ final DatagramPacket packet;
+ packet = queue.take();
+ try {
+ socket.send( packet );
+ } catch ( IOException e ) {
+ log.debug( "Could not send UDP packet to " + packet.getAddress().getHostAddress().toString() );
+ }
+ }
+ } catch ( InterruptedException e ) {
+ Thread.currentThread().interrupt();
+ } finally {
+ log.info( "UDP sender finished." );
+ }
+ }
+
+ /**
+ * Add something to the outgoing packet queue.
+ * Called from the receiving thread.
+ */
+ public void send( DatagramPacket packet )
+ {
+ if ( queue.offer( packet ) )
+ return;
+ log.warn( "Could not add packet to queue: Full" );
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/openslx/imagemaster/localrpc/ShibUser.java b/src/main/java/org/openslx/imagemaster/localrpc/ShibUser.java
new file mode 100644
index 0000000..4cba3a4
--- /dev/null
+++ b/src/main/java/org/openslx/imagemaster/localrpc/ShibUser.java
@@ -0,0 +1,13 @@
+package org.openslx.imagemaster.localrpc;
+
+import org.openslx.imagemaster.session.User;
+
+public class ShibUser extends User
+{
+
+ protected ShibUser( int userId, String login, String organization, String firstName, String lastName, String eMail, String role )
+ {
+ super( userId, login, "", organization, firstName, lastName, eMail, "", role );
+ }
+
+}
diff --git a/src/main/java/org/openslx/imagemaster/thrift/server/TBinaryProtocolSafe.java b/src/main/java/org/openslx/imagemaster/thrift/server/TBinaryProtocolSafe.java
deleted file mode 100644
index 08654d6..0000000
--- a/src/main/java/org/openslx/imagemaster/thrift/server/TBinaryProtocolSafe.java
+++ /dev/null
@@ -1,122 +0,0 @@
-package org.openslx.imagemaster.thrift.server;
-
-import java.io.UnsupportedEncodingException;
-import java.nio.ByteBuffer;
-
-import org.apache.thrift.TException;
-import org.apache.thrift.protocol.TBinaryProtocol;
-import org.apache.thrift.protocol.TMessage;
-import org.apache.thrift.protocol.TProtocol;
-import org.apache.thrift.protocol.TProtocolException;
-import org.apache.thrift.protocol.TProtocolFactory;
-import org.apache.thrift.transport.TTransport;
-
-/**
- * Binary protocol implementation for thrift.
- * Will not read messages bigger than 12MiB.
- *
- */
-public class TBinaryProtocolSafe extends TBinaryProtocol
-{
- /**
- * Factory
- */
- @SuppressWarnings( "serial" )
- public static class Factory implements TProtocolFactory
- {
-
- protected boolean strictRead_ = false;
- protected boolean strictWrite_ = true;
-
- public Factory()
- {
- this( false, true );
- }
-
- public Factory(boolean strictRead, boolean strictWrite)
- {
- strictRead_ = strictRead;
- strictWrite_ = strictWrite;
- }
-
- public TProtocol getProtocol( TTransport trans )
- {
- return new TBinaryProtocolSafe( trans, strictRead_, strictWrite_ );
- }
- }
-
- private static final int maxLen = 12 * 1024 * 1024; // 12 MiB
-
- /**
- * Constructor
- */
- public TBinaryProtocolSafe(TTransport trans)
- {
- this( trans, false, true );
- }
-
- public TBinaryProtocolSafe(TTransport trans, boolean strictRead, boolean strictWrite)
- {
- super( trans );
- strictRead_ = strictRead;
- strictWrite_ = strictWrite;
- }
-
- /**
- * Reading methods.
- */
-
- public TMessage readMessageBegin() throws TException
- {
- int size = readI32();
- if ( size > maxLen )
- throw new TProtocolException( TProtocolException.SIZE_LIMIT, "Payload too big." );
- if ( size < 0 ) {
- int version = size & VERSION_MASK;
- if ( version != VERSION_1 ) {
- throw new TProtocolException( TProtocolException.BAD_VERSION, "Bad version in readMessageBegin" );
- }
- return new TMessage( readString(), (byte) ( size & 0x000000ff ), readI32() );
- } else {
- if ( strictRead_ ) {
- throw new TProtocolException( TProtocolException.BAD_VERSION, "Missing version in readMessageBegin, old client?" );
- }
- return new TMessage( readStringBody( size ), readByte(), readI32() );
- }
- }
-
- public String readString() throws TException
- {
- int size = readI32();
- if ( size > maxLen )
- throw new TProtocolException( TProtocolException.SIZE_LIMIT, "Payload too big." );
- if ( trans_.getBytesRemainingInBuffer() >= size ) {
- try {
- String s = new String( trans_.getBuffer(), trans_.getBufferPosition(), size, "UTF-8" );
- trans_.consumeBuffer( size );
- return s;
- } catch ( UnsupportedEncodingException e ) {
- throw new TException( "JVM DOES NOT SUPPORT UTF-8" );
- }
- }
-
- return readStringBody( size );
- }
-
- public ByteBuffer readBinary() throws TException
- {
- int size = readI32();
- if ( size > maxLen )
- throw new TProtocolException( TProtocolException.SIZE_LIMIT, "Payload too big." );
- if ( trans_.getBytesRemainingInBuffer() >= size ) {
- ByteBuffer bb = ByteBuffer.wrap( trans_.getBuffer(), trans_.getBufferPosition(), size );
- trans_.consumeBuffer( size );
- return bb;
- }
-
- byte[] buf = new byte[ size ];
- trans_.readAll( buf, 0, size );
- return ByteBuffer.wrap( buf );
- }
-
-}