Skip to content

Commit 67b7a2f

Browse files
committed
dbeaver/pro#6861 add auth method for provisioning
1 parent 5e36ef4 commit 67b7a2f

3 files changed

Lines changed: 266 additions & 60 deletions

File tree

server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java

Lines changed: 164 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
8181

8282
protected static final String CHAR_BOOL_TRUE = "Y";
8383
protected static final String CHAR_BOOL_FALSE = "N";
84+
private static final String CRED_ID_PROVISIONED = "$provisioned";
85+
private static final String CRED_VALUE_PROVISIONED = "Y";
8486

8587
private static final Type MAP_STRING_OBJECT_TYPE = new TypeToken<Map<String, Object>>() {
8688
}.getType();
@@ -419,7 +421,6 @@ protected List<SMUserProvisioning> importUsers(@NotNull Connection connection, @
419421
List<SMUserProvisioning> users = userImportList.getUsers();
420422
List<SMUserProvisioning> importedUsers = new ArrayList<>();
421423
Set<String> seen = new HashSet<>();
422-
outer:
423424
for (SMUserProvisioning user : users) {
424425
Map<String, String> metaParameters = user.getMetaParameters();
425426
String effectiveUserId = user.getUserId();
@@ -435,24 +436,104 @@ protected List<SMUserProvisioning> importUsers(@NotNull Connection connection, @
435436

436437
String authRole = user.getAuthRole() == null ? userImportList.getAuthRole() : user.getAuthRole();
437438
String userId = effectiveUserId;
439+
String targetUserId = null;
440+
boolean subjectConflict = false;
438441
for (String possibleUserId : List.of(userId, userId.toLowerCase())) {
439442
if (isSubjectExists(possibleUserId)) {
440443
if (getSubjectType(possibleUserId) == SMSubjectType.team) {
441444
log.error("Cannot import user '%s': a team with this name already exists.".formatted(possibleUserId));
445+
subjectConflict = true;
442446
} else {
443447
log.info("User already exist : " + possibleUserId);
444448
setUserAuthRole(connection, possibleUserId, authRole);
445449
enableUser(connection, possibleUserId, true, null, null);
450+
targetUserId = possibleUserId;
446451
}
447-
continue outer;
452+
break;
448453
}
449454
}
450-
insertUser(connection, userId.toLowerCase(), metaParameters, true, authRole);
451-
importedUsers.add(user);
455+
if (subjectConflict) {
456+
continue;
457+
}
458+
if (targetUserId == null) {
459+
targetUserId = userId.toLowerCase();
460+
insertUser(connection, targetUserId, metaParameters, true, authRole);
461+
importedUsers.add(user);
462+
}
463+
if (CommonUtils.isNotEmpty(userImportList.getAuthProviderId())) {
464+
linkProvisionedAuthProvider(connection, targetUserId, userImportList.getAuthProviderId());
465+
}
452466
}
453467
return importedUsers;
454468
}
455469

470+
protected void linkProvisionedAuthProvider(
471+
@NotNull Connection connection,
472+
@NotNull String userId,
473+
@NotNull String authProviderId
474+
) throws SQLException {
475+
try (PreparedStatement dbStat = connection.prepareStatement(
476+
"SELECT USER_ID FROM {table_prefix}CB_USER WHERE USER_ID=? FOR UPDATE"
477+
)) {
478+
dbStat.setString(1, userId);
479+
try (ResultSet dbResult = dbStat.executeQuery()) {
480+
if (!dbResult.next()) {
481+
throw new SQLException("User '" + userId + "' not found");
482+
}
483+
}
484+
}
485+
try (PreparedStatement dbStat = connection.prepareStatement(
486+
"SELECT CRED_ID FROM {table_prefix}CB_USER_CREDENTIALS WHERE USER_ID=? AND PROVIDER_ID=?"
487+
)) {
488+
dbStat.setString(1, userId);
489+
dbStat.setString(2, authProviderId);
490+
try (ResultSet dbResult = dbStat.executeQuery()) {
491+
if (dbResult.next()) {
492+
return;
493+
}
494+
}
495+
}
496+
JDBCUtils.executeStatement(
497+
connection,
498+
"INSERT INTO {table_prefix}CB_USER_CREDENTIALS(USER_ID,PROVIDER_ID,CRED_ID,CRED_VALUE) VALUES(?,?,?,?)",
499+
userId,
500+
authProviderId,
501+
CRED_ID_PROVISIONED,
502+
CRED_VALUE_PROVISIONED
503+
);
504+
}
505+
506+
protected void linkProvisionedAuthProviders(@NotNull SMUserImportList userImportList) throws DBException {
507+
String authProviderId = userImportList.getAuthProviderId();
508+
if (CommonUtils.isEmpty(authProviderId)) {
509+
return;
510+
}
511+
try (Connection connection = database.openConnection();
512+
JDBCTransaction transaction = new JDBCTransaction(connection)) {
513+
for (SMUserProvisioning user : userImportList.getUsers()) {
514+
String userId = user.getUserId();
515+
String metaUserId = user.getMetaParameters().get(SMStandardMeta.META_USER_ID);
516+
if (CommonUtils.isNotEmpty(metaUserId)) {
517+
userId = metaUserId;
518+
}
519+
String existingUserId = null;
520+
for (String possibleUserId : List.of(userId, userId.toLowerCase())) {
521+
if (isSubjectExists(possibleUserId) && getSubjectType(possibleUserId) == SMSubjectType.user) {
522+
existingUserId = possibleUserId;
523+
break;
524+
}
525+
}
526+
if (existingUserId == null) {
527+
throw new DBException("Imported user '" + userId + "' not found");
528+
}
529+
linkProvisionedAuthProvider(connection, existingUserId, authProviderId);
530+
}
531+
transaction.commit();
532+
} catch (SQLException e) {
533+
throw new DBCException("Error linking provisioned auth provider", e);
534+
}
535+
}
536+
456537
@Override
457538
public void deleteUser(String userId) throws DBCException {
458539
invalidateAllUserTokens(userId);
@@ -1173,12 +1254,28 @@ public void setUserCredentials(
11731254
@NotNull String authProviderId,
11741255
@NotNull Map<String, Object> credentials
11751256
) throws DBException {
1176-
var existUserByCredentials = findUserByCredentials(getAuthProvider(authProviderId), credentials, false);
1257+
try (Connection dbCon = database.openConnection()) {
1258+
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
1259+
setUserCredentials(dbCon, userId, authProviderId, credentials);
1260+
txn.commit();
1261+
}
1262+
} catch (SQLException e) {
1263+
throw new DBCException("Error saving user credentials in database", e);
1264+
}
1265+
}
1266+
1267+
private void setUserCredentials(
1268+
@NotNull Connection dbCon,
1269+
@NotNull String userId,
1270+
@NotNull String authProviderId,
1271+
@NotNull Map<String, Object> credentials
1272+
) throws DBException, SQLException {
1273+
WebAuthProviderDescriptor authProvider = getAuthProvider(authProviderId);
1274+
var existUserByCredentials = findUserByCredentials(dbCon, authProvider, credentials, false);
11771275
if (existUserByCredentials != null && !existUserByCredentials.equals(userId)) {
11781276
throw new DBException("Another user is already linked to the specified credentials");
11791277
}
11801278
List<String[]> transformedCredentials;
1181-
WebAuthProviderDescriptor authProvider = getAuthProvider(authProviderId);
11821279
if (authProvider.isCaseInsensitive() && !isSubjectExists(userId) && isSubjectExists(userId.toLowerCase())) {
11831280
log.warn("User with id '" + userId + "' not found, credentials will be set for the user: " + userId.toLowerCase());
11841281
userId = userId.toLowerCase();
@@ -1199,35 +1296,28 @@ public void setUserCredentials(
11991296
} catch (Exception e) {
12001297
throw new DBCException(e.getMessage(), e);
12011298
}
1202-
try (Connection dbCon = database.openConnection()) {
1203-
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
1204-
JDBCUtils.executeStatement(
1205-
dbCon,
1206-
"DELETE FROM {table_prefix}CB_USER_CREDENTIALS WHERE USER_ID=? AND PROVIDER_ID=?",
1207-
userId,
1208-
authProvider.getId()
1209-
);
1210-
if (!CommonUtils.isEmpty(credentials)) {
1211-
try (PreparedStatement dbStat = dbCon.prepareStatement(
1212-
"INSERT INTO {table_prefix}CB_USER_CREDENTIALS" +
1213-
"(USER_ID,PROVIDER_ID,CRED_ID,CRED_VALUE) VALUES(?,?,?,?)")
1214-
) {
1215-
for (String[] cred : transformedCredentials) {
1216-
if (cred == null) {
1217-
continue;
1218-
}
1219-
dbStat.setString(1, userId);
1220-
dbStat.setString(2, authProvider.getId());
1221-
dbStat.setString(3, cred[0]);
1222-
dbStat.setString(4, cred[1]);
1223-
dbStat.execute();
1224-
}
1299+
JDBCUtils.executeStatement(
1300+
dbCon,
1301+
"DELETE FROM {table_prefix}CB_USER_CREDENTIALS WHERE USER_ID=? AND PROVIDER_ID=?",
1302+
userId,
1303+
authProvider.getId()
1304+
);
1305+
if (!CommonUtils.isEmpty(credentials)) {
1306+
try (PreparedStatement dbStat = dbCon.prepareStatement(
1307+
"INSERT INTO {table_prefix}CB_USER_CREDENTIALS" +
1308+
"(USER_ID,PROVIDER_ID,CRED_ID,CRED_VALUE) VALUES(?,?,?,?)")
1309+
) {
1310+
for (String[] cred : transformedCredentials) {
1311+
if (cred == null) {
1312+
continue;
12251313
}
1314+
dbStat.setString(1, userId);
1315+
dbStat.setString(2, authProvider.getId());
1316+
dbStat.setString(3, cred[0]);
1317+
dbStat.setString(4, cred[1]);
1318+
dbStat.execute();
12261319
}
1227-
txn.commit();
12281320
}
1229-
} catch (SQLException e) {
1230-
throw new DBCException("Error saving user credentials in database", e);
12311321
}
12321322
log.info(String.format("Set credentials for user: [userId=%s,providerId=%s]", userId, authProviderId));
12331323
}
@@ -1253,16 +1343,31 @@ private String findUserByCredentials(
12531343
@NotNull Map<String, Object> authParameters,
12541344
boolean onlyActive // throws exception if user is inactive
12551345
) throws DBException {
1256-
String userId = findUserByCredentials(authProvider, authParameters, onlyActive, false);
1346+
try (Connection dbCon = database.openConnection()) {
1347+
return findUserByCredentials(dbCon, authProvider, authParameters, onlyActive);
1348+
} catch (SQLException e) {
1349+
throw new DBCException("Error while searching credentials", e);
1350+
}
1351+
}
1352+
1353+
@Nullable
1354+
private String findUserByCredentials(
1355+
@NotNull Connection dbCon,
1356+
@NotNull WebAuthProviderDescriptor authProvider,
1357+
@NotNull Map<String, Object> authParameters,
1358+
boolean onlyActive
1359+
) throws DBCException {
1360+
String userId = findUserByCredentials(dbCon, authProvider, authParameters, onlyActive, false);
12571361
if (userId == null && authProvider.isCaseInsensitive()) {
12581362
// try to find user id with lower case is auth provider is case-insensitive
1259-
return findUserByCredentials(authProvider, authParameters, onlyActive, true);
1363+
return findUserByCredentials(dbCon, authProvider, authParameters, onlyActive, true);
12601364
}
12611365
return userId;
12621366
}
12631367

12641368
@Nullable
12651369
private String findUserByCredentials(
1370+
@NotNull Connection dbCon,
12661371
@NotNull WebAuthProviderDescriptor authProvider,
12671372
@NotNull Map<String, Object> authParameters,
12681373
boolean onlyActive,
@@ -1303,34 +1408,32 @@ private String findUserByCredentials(
13031408
.append(joinAlias).append("CRED_ID=? AND ")
13041409
.append(joinAlias).append("CRED_VALUE=?");
13051410
}
1306-
try (Connection dbCon = database.openConnection()) {
1307-
try (PreparedStatement dbStat = dbCon.prepareStatement(sql.toString())) {
1308-
dbStat.setString(1, authProvider.getId());
1309-
int param = 2;
1310-
for (Map.Entry<String, String> credEntry : identCredentials.entrySet()) {
1311-
dbStat.setString(param++, credEntry.getKey());
1312-
dbStat.setString(param++, credEntry.getValue());
1313-
}
1314-
1315-
try (ResultSet dbResult = dbStat.executeQuery()) {
1316-
String userId = null;
1317-
boolean isActive = false;
1318-
while (dbResult.next()) {
1319-
String credUserId = dbResult.getString(1);
1320-
isActive = CHAR_BOOL_TRUE.equals(dbResult.getString(2));
1321-
if (userId == null) {
1322-
userId = credUserId;
1323-
} else if (!userId.equals(credUserId)) {
1324-
log.error("Multiple users associated with the same credentials! " + credUserId + ", " + userId);
1325-
}
1326-
}
1411+
try (PreparedStatement dbStat = dbCon.prepareStatement(sql.toString())) {
1412+
dbStat.setString(1, authProvider.getId());
1413+
int param = 2;
1414+
for (Map.Entry<String, String> credEntry : identCredentials.entrySet()) {
1415+
dbStat.setString(param++, credEntry.getKey());
1416+
dbStat.setString(param++, credEntry.getValue());
1417+
}
13271418

1328-
if (userId != null && onlyActive && !isActive) {
1329-
throw new DBCException("User account is locked");
1419+
try (ResultSet dbResult = dbStat.executeQuery()) {
1420+
String userId = null;
1421+
boolean isActive = false;
1422+
while (dbResult.next()) {
1423+
String credUserId = dbResult.getString(1);
1424+
isActive = CHAR_BOOL_TRUE.equals(dbResult.getString(2));
1425+
if (userId == null) {
1426+
userId = credUserId;
1427+
} else if (!userId.equals(credUserId)) {
1428+
log.error("Multiple users associated with the same credentials! " + credUserId + ", " + userId);
13301429
}
1430+
}
13311431

1332-
return userId;
1432+
if (userId != null && onlyActive && !isActive) {
1433+
throw new DBCException("User account is locked");
13331434
}
1435+
1436+
return userId;
13341437
}
13351438
} catch (SQLException e) {
13361439
throw new DBCException("Error while searching credentials", e);
@@ -1341,7 +1444,7 @@ private String findUserByCredentials(
13411444
public Map<String, Object> getUserCredentials(String userId, String authProviderId) throws DBCException {
13421445
WebAuthProviderDescriptor authProvider = getAuthProvider(authProviderId);
13431446
Map<String, Object> creds = getUserCredentials(authProvider, userId);
1344-
if (creds.isEmpty() && authProvider.isCaseInsensitive()) {
1447+
if (creds.isEmpty() && authProvider.isCaseInsensitive() && !isSubjectExists(userId)) {
13451448
return getUserCredentials(authProvider, userId.toLowerCase());
13461449
}
13471450
return creds;
@@ -1352,10 +1455,11 @@ private Map<String, Object> getUserCredentials(WebAuthProviderDescriptor authPro
13521455
try (Connection dbCon = database.openConnection()) {
13531456
try (PreparedStatement dbStat = dbCon.prepareStatement(
13541457
"SELECT CRED_ID,CRED_VALUE FROM {table_prefix}CB_USER_CREDENTIALS\n" +
1355-
"WHERE USER_ID=? AND PROVIDER_ID=?")) {
1458+
"WHERE USER_ID=? AND PROVIDER_ID=? AND CRED_ID<>?")) {
13561459
dbStat.setString(1, userId);
13571460

13581461
dbStat.setString(2, authProvider.getId());
1462+
dbStat.setString(3, CRED_ID_PROVISIONED);
13591463

13601464
try (ResultSet dbResult = dbStat.executeQuery()) {
13611465
Map<String, Object> credentials = new LinkedHashMap<>();

server/test/io.cloudbeaver.test.platform/src/io/cloudbeaver/test/platform/CEServerTestSuite.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.cloudbeaver.model.session.WebSessionProjectTest;
2525
import io.cloudbeaver.model.session.WebSessionTest;
2626
import io.cloudbeaver.test.platform.admin.AdminCreateUserTest;
27+
import io.cloudbeaver.test.platform.admin.AdminImportUsersTest;
2728
import io.cloudbeaver.test.platform.admin.AdminLastLoginTimeTest;
2829
import io.cloudbeaver.test.platform.sql.*;
2930
import org.junit.jupiter.api.AfterAll;
@@ -45,6 +46,7 @@
4546
WebSessionProjectTest.class,
4647
WebNavigatorNodeInfoTest.class,
4748
AdminCreateUserTest.class,
49+
AdminImportUsersTest.class,
4850
AdminLastLoginTimeTest.class,
4951
GenerateSQLResultSetTest.class,
5052
RowIdResultSetTest.class,

0 commit comments

Comments
 (0)