summaryrefslogtreecommitdiffstats
path: root/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/database/mappers/DbUser.java
blob: 852695d37bdf4cc01d480a48cc95f77d3011860f (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
package org.openslx.bwlp.sat.database.mappers;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openslx.bwlp.sat.RuntimeConfig;
import org.openslx.bwlp.sat.database.Database;
import org.openslx.bwlp.sat.database.MysqlConnection;
import org.openslx.bwlp.sat.database.MysqlStatement;
import org.openslx.bwlp.sat.database.Paginator;
import org.openslx.bwlp.sat.database.models.LocalUser;
import org.openslx.bwlp.thrift.iface.SatelliteUserConfig;
import org.openslx.bwlp.thrift.iface.TNotFoundException;
import org.openslx.bwlp.thrift.iface.UserInfo;
import org.openslx.util.QuickTimer;
import org.openslx.util.QuickTimer.Task;
import org.openslx.util.TimeoutHashMap;
import org.openslx.util.Util;

public class DbUser {

	private static boolean legacyUsersExist = false;

	public static class User {
		public final UserInfo ui;
		public final LocalUser local;

		public User(UserInfo ui, LocalUser local) {
			this.ui = ui;
			this.local = local;
		}
	}

	private static final Logger LOGGER = LogManager.getLogger(DbUser.class);

	private static Map<String, User> userCache = new TimeoutHashMap<>(TimeUnit.MINUTES.toMillis(15));

	/**
	 * Get all users, starting at page <code>page</code>.
	 * This function will return a maximum of {@link #PER_PAGE} results, so
	 * you might need to call this method several times.
	 * 
	 * @param page Page to return. The first page is page 0.
	 * @return List of {@link UserInfo}
	 * @throws SQLException
	 */
	public static List<UserInfo> getAll(int page) throws SQLException {
		if (page < 0)
			return new ArrayList<>(1);
		try (MysqlConnection connection = Database.getConnection()) {
			MysqlStatement stmt = connection.prepareStatement("SELECT userid, firstname, lastname, email, organizationid"
					+ " FROM user ORDER BY userid ASC " + Paginator.limitStatement(page));
			ResultSet rs = stmt.executeQuery();
			List<UserInfo> list = new ArrayList<>();
			while (rs.next()) {
				list.add(new UserInfo(rs.getString("userid"), rs.getString("firstname"),
						rs.getString("lastname"), rs.getString("email"), rs.getString("organizationid")));
			}
			return list;
		} catch (SQLException e) {
			LOGGER.error("Query failed in DbUser.getAll()", e);
			throw e;
		}
	}

	public static UserInfo getOrNull(String userId) throws SQLException {
		try (MysqlConnection connection = Database.getConnection()) {
			MysqlStatement stmt = connection.prepareStatement("SELECT userid, firstname, lastname, email, organizationid"
					+ " FROM user WHERE userid = :userid");
			stmt.setString("userid", userId);
			ResultSet rs = stmt.executeQuery();
			if (rs.next()) {
				return new UserInfo(rs.getString("userid"), rs.getString("firstname"),
						rs.getString("lastname"), rs.getString("email"), rs.getString("organizationid"));
			}
			return null;
		} catch (SQLException e) {
			LOGGER.error("Query failed in DbUser.getAll()", e);
			throw e;
		}
	}

	/**
	 * Get local-only information for given user.
	 * 
	 * @param user {@link UserInfo} instance representing the user
	 * @return {@link LocalUser} instance matching the given user, or null if
	 *         not found
	 * @throws SQLException
	 */
	public static LocalUser getLocalData(UserInfo user) throws SQLException {
		try (MysqlConnection connection = Database.getConnection()) {
			MysqlStatement stmt = connection.prepareStatement("SELECT lastlogin, canlogin, issuperuser, emailnotifications"
					+ " FROM user WHERE userid = :userid LIMIT 1");
			stmt.setString("userid", user.userId);
			ResultSet rs = stmt.executeQuery();
			if (!rs.next())
				return null;
			return new LocalUser(rs.getLong("lastlogin"), rs.getBoolean("canlogin"),
					rs.getBoolean("issuperuser"), rs.getBoolean("emailnotifications"));
		} catch (SQLException e) {
			LOGGER.error("Query failed in DbUser.getLocalData()", e);
			throw e;
		}
	}

	public static void writeUserConfig(UserInfo user, SatelliteUserConfig config) throws SQLException {
		try (MysqlConnection connection = Database.getConnection()) {
			MysqlStatement stmt = connection.prepareStatement("UPDATE user SET"
					+ " emailnotifications = :emailnotifications    WHERE userid = :userid");
			stmt.setString("userid", user.userId);
			stmt.setBoolean("emailnotifications", config.emailNotifications);
			stmt.executeUpdate();
			connection.commit();
		} catch (SQLException e) {
			LOGGER.error("Query failed in DbUser.writeUserConfig()", e);
			throw e;
		}
	}

	/**
	 * Insert given user into db (if not already existent), otherwise just
	 * update the "lastlogin" field.
	 * 
	 * @param ui {@link UserInfo}
	 * @throws SQLException
	 */
	public static void writeUserOnLogin(UserInfo ui) throws SQLException {
		writeUser(ui, true);
	}

	public static void writeUserOnReplication(UserInfo ui) throws SQLException {
		writeUser(ui, false);
	}

	private static void writeUser(UserInfo ui, boolean isLogin) throws SQLException {
		// TODO: Ugly hardcode solution - should be queried from DB, with a nice helper class
		if (ui.firstName.length() > 50) {
			ui.firstName = ui.firstName.substring(0, 50);
		}
		if (ui.lastName.length() > 50) {
			ui.lastName = ui.lastName.substring(0, 50);
		}
		if (ui.eMail.length() > 100) {
			ui.eMail = ui.eMail.substring(0, 100);
		}
		boolean recheckLegacy = true;
		try (MysqlConnection connection = Database.getConnection()) {
			if (!legacyUsersExist || !tryLegacyUserUpdate(connection, ui)) {
				// No legacy messed up account found - use normal way
				MysqlStatement insUpStmt;
				if (isLogin) {
					insUpStmt = connection.prepareStatement("INSERT INTO user"
							+ " (userid, firstname, lastname, email, organizationid, lastlogin, canlogin, issuperuser, emailnotifications)"
							+ " VALUES"
							+ " (:userid, :firstname, :lastname, :email, :organizationid, UNIX_TIMESTAMP(), :canlogin, 0, 1)"
							+ " ON DUPLICATE KEY UPDATE lastlogin = UNIX_TIMESTAMP(), email = VALUES(email),"
							+ " firstname = VALUES(firstname), lastname = VALUES(lastname), organizationid = VALUES(organizationid)");
					insUpStmt.setBoolean("canlogin", RuntimeConfig.allowLoginByDefault());
				} else {
					insUpStmt = connection.prepareStatement("INSERT INTO user"
							+ " (userid, firstname, lastname, email, organizationid, canlogin, issuperuser, emailnotifications)"
							+ " VALUES"
							+ " (:userid, :firstname, :lastname, :email, :organizationid, 0, 0, 0)"
							+ " ON DUPLICATE KEY UPDATE email = VALUES(email),"
							+ " firstname = VALUES(firstname), lastname = VALUES(lastname), organizationid = VALUES(organizationid)");
				}
				insUpStmt.setString("userid", ui.userId);
				insUpStmt.setString("firstname", ui.firstName);
				insUpStmt.setString("lastname", ui.lastName);
				insUpStmt.setString("email", ui.eMail);
				insUpStmt.setString("organizationid", ui.organizationId);
				insUpStmt.executeUpdate();
				recheckLegacy = false;
			}
			connection.commit();
		} catch (SQLException e) {
			LOGGER.error("Query failed in DbUser.writeUserOnLogin()", e);
			throw e;
		}
		if (recheckLegacy) {
			// Run check again
			checkIfLegacyUsersExist();
		}
	}

	/**
	 * Try to update a legacy imported user and fix the entry
	 */
	private static boolean tryLegacyUserUpdate(MysqlConnection connection, UserInfo ui) throws SQLException {
		// Well... don't look at the code below. The old server had a bug where it wrote
		// wrong user ids to the data base. If we imported old data, the user info table
		// might contain a messed up entry for the given user. So instead of a nice
		// INSERT ... ON DUPLICATE KEY we have to do this funny stuff.
		MysqlStatement findStmt = connection.prepareStatement("SELECT userid FROM user"
				+ " WHERE firstname = :firstname AND lastname = :lastname AND email = :email");
		findStmt.setString("firstname", ui.firstName);
		findStmt.setString("lastname", ui.lastName);
		findStmt.setString("email", "@" + ui.eMail + "@");
		ResultSet rs = findStmt.executeQuery();
		if (!rs.next())
			return false;
		// We actually found an old imported entry - just update
		String oldId = rs.getString("userid");
		MysqlStatement insUpStmt = connection.prepareStatement("UPDATE IGNORE user"
				+ " SET lastlogin = UNIX_TIMESTAMP(), email = :email, userid = :newuserid, organizationid = :organizationid,"
				+ " emailnotifications = 1 WHERE userid = :olduserid");
		insUpStmt.setString("newuserid", ui.userId);
		insUpStmt.setString("email", ui.eMail);
		insUpStmt.setString("organizationid", ui.organizationId);
		insUpStmt.setString("olduserid", oldId);
		insUpStmt.executeUpdate();
		if (!ui.userId.equals(oldId)) {
			// Be extra safe: in case the update failed (dup key?) we patch the old entry so it doesn't look like an old one anymore
			MysqlStatement fixStmt = connection.prepareStatement("UPDATE user SET"
					+ " email = 'void', emailnotifications = 0 WHERE userid = :olduserid AND email LIKE '@%'");
			fixStmt.setString("olduserid", oldId);
			fixStmt.executeUpdate();
		}
		return true;
	}

	public static void checkIfLegacyUsersExist() {
		QuickTimer.scheduleOnce(new Task() {
			@Override
			public void fire() {
				try (MysqlConnection connection = Database.getConnection()) {
					MysqlStatement stmt = connection.prepareStatement("SELECT userid FROM user"
							+ " WHERE email LIKE '@%@' LIMIT 1");
					ResultSet rs = stmt.executeQuery();
					legacyUsersExist = rs.next();
				} catch (SQLException e) {
					LOGGER.error("Query failed in DbUser.checkIfLegacyUsersExist()", e);
				}
				LOGGER.info("Imported legacy users exist: " + Boolean.toString(legacyUsersExist));
			}
		});
	}

	public static User getCached(String userId) throws SQLException, TNotFoundException {
		synchronized (DbUser.class) {
			User user = userCache.get(userId);
			if (user != null)
				return user;
		}
		try (MysqlConnection connection = Database.getConnection()) {
			MysqlStatement stmt = connection.prepareStatement("SELECT userid, firstname, lastname, email, organizationid,"
					+ " lastlogin, canlogin, issuperuser, emailnotifications"
					+ " FROM user WHERE userid = :userid");
			stmt.setString("userid", userId);
			ResultSet rs = stmt.executeQuery();
			if (!rs.next())
				throw new TNotFoundException();
			UserInfo userInfo = new UserInfo(rs.getString("userid"), rs.getString("firstname"),
					rs.getString("lastname"), rs.getString("email"), rs.getString("organizationid"));
			LocalUser local = new LocalUser(rs.getLong("lastlogin"), rs.getBoolean("canlogin"),
					rs.getBoolean("issuperuser"), rs.getBoolean("emailnotifications"));
			User user = new User(userInfo, local);
			synchronized (DbUser.class) {
				userCache.put(userInfo.userId, user);
			}
			return user;
		} catch (SQLException e) {
			LOGGER.error("Query failed in DbUser.getCached()", e);
			throw e;
		}
	}

	/**
	 * @return list of users who didn't log in for at least 180 days
	 */
	public static List<UserInfo> getInactive() throws SQLException {
		long cutoff = Util.unixTime() - TimeUnit.DAYS.toSeconds(180);
		try (MysqlConnection connection = Database.getConnection()) {
			MysqlStatement stmt = connection.prepareStatement("SELECT userid, firstname, lastname, email, organizationid"
					+ " FROM user WHERE lastlogin < :cutoff AND canlogin <> 0 AND issuperuser = 0");
			stmt.setLong("cutoff", cutoff);
			ResultSet rs = stmt.executeQuery();
			List<UserInfo> list = new ArrayList<>();
			while (rs.next()) {
				list.add(new UserInfo(rs.getString("userid"), rs.getString("firstname"),
						rs.getString("lastname"), rs.getString("email"), rs.getString("organizationid")));
			}
			return list;
		} catch (SQLException e) {
			LOGGER.error("Query failed in getInactive()", e);
			throw e;
		}
	}

	/**
	 * Delete given user from database. Not that this might fail due to
	 * constraints.
	 * 
	 * @param user the user to delete
	 */
	public static boolean deleteUser(UserInfo user) throws SQLException {
		try (MysqlConnection connection = Database.getConnection()) {
			MysqlStatement stmt = connection.prepareStatement("DELETE FROM user WHERE userid = :userid");
			stmt.setString("userid", user.userId);
			try {
				int num = stmt.executeUpdate();
				connection.commit();
				return num > 0;
			} catch (SQLException e) {
				connection.rollback();
				return false;
			}
		} catch (SQLException e) {
			LOGGER.error("Query failed in deleteUser()", e);
			throw e;
		}
	}

}