@@ -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 <>();
0 commit comments