diff --git a/CHANGELOG.md b/CHANGELOG.md index c93e78c..1c6739f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,18 +23,21 @@ CREATE TABLE IF NOT EXISTS oauth_clients ( FOREIGN KEY(app_id) REFERENCES apps(app_id) ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS oauth_revoke ( +CREATE TABLE IF NOT EXISTS oauth_sessions ( + gid VARCHAR(255), app_id VARCHAR(64) DEFAULT 'public', - target_type VARCHAR(16) NOT NULL, - target_value VARCHAR(128) NOT NULL, - timestamp BIGINT UNSIGNED NOT NULL, - exp BIGINT UNSIGNED NOT NULL, - PRIMARY KEY (app_id, target_type, target_value), - FOREIGN KEY(app_id) REFERENCES apps(app_id) ON DELETE CASCADE + client_id VARCHAR(255) NOT NULL, + session_handle VARCHAR(128), + external_refresh_token VARCHAR(255) UNIQUE, + internal_refresh_token VARCHAR(255) UNIQUE, + jti TEXT NOT NULL, + exp BIGINT NOT NULL, + PRIMARY KEY (gid), + FOREIGN KEY(app_id, client_id) REFERENCES oauth_clients(app_id, client_id) ON DELETE CASCADE ); -CREATE INDEX oauth_revoke_timestamp_index ON oauth_revoke(timestamp DESC, app_id DESC); -CREATE INDEX oauth_revoke_exp_index ON oauth_revoke(exp DESC); +CREATE INDEX IF NOT EXISTS oauth_session_exp_index ON oauth_sessions(exp DESC); +CREATE INDEX IF NOT EXISTS oauth_session_external_refresh_token_index ON oauth_sessions(app_id, external_refresh_token DESC); CREATE TABLE oauth_m2m_tokens ( app_id VARCHAR(64) DEFAULT 'public', diff --git a/src/main/java/io/supertokens/storage/mysql/Start.java b/src/main/java/io/supertokens/storage/mysql/Start.java index c0b4ddd..1900324 100644 --- a/src/main/java/io/supertokens/storage/mysql/Start.java +++ b/src/main/java/io/supertokens/storage/mysql/Start.java @@ -57,8 +57,8 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateThirdPartyIdException; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.multitenancy.sqlStorage.MultitenancySQLStorage; +import io.supertokens.pluginInterface.oauth.OAuthClient; import io.supertokens.pluginInterface.oauth.OAuthLogoutChallenge; -import io.supertokens.pluginInterface.oauth.OAuthRevokeTargetType; import io.supertokens.pluginInterface.oauth.OAuthStorage; import io.supertokens.pluginInterface.oauth.exception.DuplicateOAuthLogoutChallengeException; import io.supertokens.pluginInterface.oauth.exception.OAuthClientNotFoundException; @@ -3042,26 +3042,30 @@ public int countUsersThatHaveMoreThanOneLoginMethodOrTOTPEnabledAndActiveSince(A } @Override - public boolean doesOAuthClientIdExist(AppIdentifier appIdentifier, String clientId) - throws StorageQueryException { + public OAuthClient getOAuthClientById(AppIdentifier appIdentifier, String clientId) + throws StorageQueryException, OAuthClientNotFoundException { try { - return OAuthQueries.doesOAuthClientIdExist(this, clientId, appIdentifier); + OAuthClient client = OAuthQueries.getOAuthClientById(this, clientId, appIdentifier); + if (client == null) { + throw new OAuthClientNotFoundException(); + } + return client; } catch (SQLException e) { throw new StorageQueryException(e); } } @Override - public void addOrUpdateOauthClient(AppIdentifier appIdentifier, String clientId, boolean isClientCredentialsOnly) + public void addOrUpdateOauthClient(AppIdentifier appIdentifier, String clientId, String clientSecret, boolean isClientCredentialsOnly, boolean enableRefreshTokenRotation) throws StorageQueryException, TenantOrAppNotFoundException { try { - OAuthQueries.addOrUpdateOauthClient(this, appIdentifier, clientId, isClientCredentialsOnly); + OAuthQueries.addOrUpdateOauthClient(this, appIdentifier, clientId, clientSecret, isClientCredentialsOnly, enableRefreshTokenRotation); } catch (SQLException e) { if (e instanceof SQLIntegrityConstraintViolationException) { + String errorMessage = e.getMessage(); MySQLConfig config = Config.getConfig(this); - String serverMessage = e.getMessage(); - if (isForeignKeyConstraintError(serverMessage, config.getAppsTable(), "app_id")) { + if (isForeignKeyConstraintError(errorMessage, config.getOAuthClientsTable(), "app_id")) { throw new TenantOrAppNotFoundException(appIdentifier); } } @@ -3079,38 +3083,48 @@ public boolean deleteOAuthClient(AppIdentifier appIdentifier, String clientId) t } @Override - public List listOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException { + public List getOAuthClients(AppIdentifier appIdentifier, List clientIds) throws StorageQueryException { try { - return OAuthQueries.listOAuthClients(this, appIdentifier); + return OAuthQueries.getOAuthClients(this, appIdentifier, clientIds); } catch (SQLException e) { throw new StorageQueryException(e); } } @Override - public void revokeOAuthTokensBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType targetType, String targetValue, long exp) - throws StorageQueryException, TenantOrAppNotFoundException { + public boolean revokeOAuthTokenByGID(AppIdentifier appIdentifier, String gid) throws StorageQueryException { try { - OAuthQueries.revokeOAuthTokensBasedOnTargetFields(this, appIdentifier, targetType, targetValue, exp); + return OAuthQueries.deleteOAuthSessionByGID(this, appIdentifier, gid); } catch (SQLException e) { - if (e instanceof SQLIntegrityConstraintViolationException) { - MySQLConfig config = Config.getConfig(this); - String serverMessage = e.getMessage(); + throw new StorageQueryException(e); + } + } - if (isForeignKeyConstraintError(serverMessage, config.getAppsTable(), "app_id")) { - throw new TenantOrAppNotFoundException(appIdentifier); - } - } + @Override + public boolean revokeOAuthTokenByClientId(AppIdentifier appIdentifier, String clientId) + throws StorageQueryException { + try { + return OAuthQueries.deleteOAuthSessionByClientId(this, appIdentifier, clientId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public boolean revokeOAuthTokenByJTI(AppIdentifier appIdentifier, String gid, String jti) + throws StorageQueryException { + try { + return OAuthQueries.deleteJTIFromOAuthSession(this, appIdentifier, gid, jti); + } catch (SQLException e) { throw new StorageQueryException(e); } - } @Override - public boolean isOAuthTokenRevokedBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType[] targetTypes, String[] targetValues, long issuedAt) + public boolean revokeOAuthTokenBySessionHandle(AppIdentifier appIdentifier, String sessionHandle) throws StorageQueryException { try { - return OAuthQueries.isOAuthTokenRevokedBasedOnTargetFields(this, appIdentifier, targetTypes, targetValues, issuedAt); + return OAuthQueries.deleteOAuthSessionBySessionHandle(this, appIdentifier, sessionHandle); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -3123,10 +3137,10 @@ public void addOAuthM2MTokenForStats(AppIdentifier appIdentifier, String clientI OAuthQueries.addOAuthM2MTokenForStats(this, appIdentifier, clientId, iat, exp); } catch (SQLException e) { if (e instanceof SQLIntegrityConstraintViolationException) { + String errorMessage = e.getMessage(); MySQLConfig config = Config.getConfig(this); - String serverMessage = e.getMessage(); - if (isForeignKeyConstraintError(serverMessage, config.getOAuthClientsTable(), "client_id")) { + if (isForeignKeyConstraintError(errorMessage, config.getOAuthM2MTokensTable(), "client_id")) { throw new OAuthClientNotFoundException(); } } @@ -3135,9 +3149,9 @@ public void addOAuthM2MTokenForStats(AppIdentifier appIdentifier, String clientI } @Override - public void cleanUpExpiredAndRevokedOAuthTokensList() throws StorageQueryException { + public void deleteExpiredOAuthM2MTokens(long exp) throws StorageQueryException { try { - OAuthQueries.cleanUpExpiredAndRevokedOAuthTokensList(this); + OAuthQueries.deleteExpiredOAuthM2MTokens(this, exp); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -3145,18 +3159,20 @@ public void cleanUpExpiredAndRevokedOAuthTokensList() throws StorageQueryExcepti @Override public void addOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge, String clientId, - String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) + String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) throws StorageQueryException, DuplicateOAuthLogoutChallengeException, OAuthClientNotFoundException { try { OAuthQueries.addOAuthLogoutChallenge(this, appIdentifier, challenge, clientId, postLogoutRedirectionUri, sessionHandle, state, timeCreated); } catch (SQLException e) { if (e instanceof SQLIntegrityConstraintViolationException) { + String errorMessage = e.getMessage(); MySQLConfig config = Config.getConfig(this); - String serverMessage = e.getMessage(); - if (isPrimaryKeyError(serverMessage, config.getOAuthLogoutChallengesTable())) { + + + if (isPrimaryKeyError(errorMessage, config.getOAuthLogoutChallengesTable(), "challenge")) { throw new DuplicateOAuthLogoutChallengeException(); - } - else if (isForeignKeyConstraintError(serverMessage, config.getOAuthClientsTable(), "client_id")) { + } else if (isForeignKeyConstraintError(errorMessage, config.getOAuthLogoutChallengesTable(), + "client_id")) { throw new OAuthClientNotFoundException(); } } @@ -3191,6 +3207,47 @@ public void deleteOAuthLogoutChallengesBefore(long time) throws StorageQueryExce } } + @Override + public void createOrUpdateOAuthSession(AppIdentifier appIdentifier, String gid, String clientId, + String externalRefreshToken, String internalRefreshToken, + String sessionHandle, List jtis, long exp) + throws StorageQueryException, OAuthClientNotFoundException { + try { + OAuthQueries.createOrUpdateOAuthSession(this, appIdentifier, gid, clientId, externalRefreshToken, + internalRefreshToken, sessionHandle, jtis, exp); + } catch (SQLException e) { + if (e instanceof SQLIntegrityConstraintViolationException) { + String errorMessage = e.getMessage(); + MySQLConfig config = Config.getConfig(this); + + if (isForeignKeyConstraintError(errorMessage, config.getOAuthSessionsTable(), + "client_id")) { + throw new OAuthClientNotFoundException(); + } + } + throw new StorageQueryException(e); + } + } + + @Override + public String getRefreshTokenMapping(AppIdentifier appIdentifier, String externalRefreshToken) + throws StorageQueryException { + try { + return OAuthQueries.getRefreshTokenMapping(this, appIdentifier, externalRefreshToken); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public void deleteExpiredOAuthSessions(long exp) throws StorageQueryException { + try { + OAuthQueries.deleteExpiredOAuthSessions(this, exp); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + @Override public int countTotalNumberOfOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException { try { @@ -3226,7 +3283,26 @@ public int countTotalNumberOfOAuthM2MTokensAlive(AppIdentifier appIdentifier) th return OAuthQueries.countTotalNumberOfOAuthM2MTokensAlive(this, appIdentifier); } catch (SQLException e) { throw new StorageQueryException(e); + } } + + @Override + public boolean isOAuthTokenRevokedByGID(AppIdentifier appIdentifier, String gid) throws StorageQueryException { + try { + return !OAuthQueries.isOAuthSessionExistsByGID(this, appIdentifier, gid); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public boolean isOAuthTokenRevokedByJTI(AppIdentifier appIdentifier, String gid, String jti) + throws StorageQueryException { + try { + return !OAuthQueries.isOAuthSessionExistsByJTI(this, appIdentifier, gid, jti); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } public static boolean isEnabledForDeadlockTesting() { diff --git a/src/main/java/io/supertokens/storage/mysql/config/MySQLConfig.java b/src/main/java/io/supertokens/storage/mysql/config/MySQLConfig.java index e514beb..85d2e74 100644 --- a/src/main/java/io/supertokens/storage/mysql/config/MySQLConfig.java +++ b/src/main/java/io/supertokens/storage/mysql/config/MySQLConfig.java @@ -377,14 +377,14 @@ public String getOAuthClientsTable() { return addPrefixToTableName("oauth_clients"); } - public String getOAuthRevokeTable() { - return addPrefixToTableName("oauth_revoke"); - } - public String getOAuthM2MTokensTable() { return addPrefixToTableName("oauth_m2m_tokens"); } + public String getOAuthSessionsTable() { + return addPrefixToTableName("oauth_sessions"); + } + public String getOAuthLogoutChallengesTable() { return addPrefixToTableName("oauth_logout_challenges"); } @@ -714,4 +714,5 @@ public String getConnectionPoolId() { public String getTablePrefix() { return mysql_table_names_prefix; } + } diff --git a/src/main/java/io/supertokens/storage/mysql/queries/GeneralQueries.java b/src/main/java/io/supertokens/storage/mysql/queries/GeneralQueries.java index e9f2059..956faa0 100644 --- a/src/main/java/io/supertokens/storage/mysql/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/storage/mysql/queries/GeneralQueries.java @@ -422,13 +422,13 @@ public static void createTablesIfNotExists(Start start, Connection con) throws S update(con, OAuthQueries.getQueryToCreateOAuthClientTable(start), NO_OP_SETTER); } - if (!doesTableExists(start, con, Config.getConfig(start).getOAuthRevokeTable())) { + if (!doesTableExists(start, con, Config.getConfig(start).getOAuthSessionsTable())) { getInstance(start).addState(CREATING_NEW_TABLE, null); - update(con, OAuthQueries.getQueryToCreateOAuthRevokeTable(start), NO_OP_SETTER); + update(con, OAuthQueries.getQueryToCreateOAuthSessionsTable(start), NO_OP_SETTER); // index - update(con, OAuthQueries.getQueryToCreateOAuthRevokeTimestampIndex(start), NO_OP_SETTER); - update(con, OAuthQueries.getQueryToCreateOAuthRevokeExpIndex(start), NO_OP_SETTER); + update(con, OAuthQueries.getQueryToCreateOAuthSessionsExpIndex(start), NO_OP_SETTER); + update(con, OAuthQueries.getQueryToCreateOAuthSessionsExternalRefreshTokenIndex(start), NO_OP_SETTER); } if (!doesTableExists(start, con, Config.getConfig(start).getOAuthM2MTokensTable())) { diff --git a/src/main/java/io/supertokens/storage/mysql/queries/OAuthQueries.java b/src/main/java/io/supertokens/storage/mysql/queries/OAuthQueries.java index bd72880..0a7f0cd 100644 --- a/src/main/java/io/supertokens/storage/mysql/queries/OAuthQueries.java +++ b/src/main/java/io/supertokens/storage/mysql/queries/OAuthQueries.java @@ -1,17 +1,20 @@ package io.supertokens.storage.mysql.queries; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.oauth.OAuthClient; import io.supertokens.pluginInterface.oauth.OAuthLogoutChallenge; -import io.supertokens.pluginInterface.oauth.OAuthRevokeTargetType; import io.supertokens.storage.mysql.Start; import io.supertokens.storage.mysql.config.Config; import io.supertokens.storage.mysql.utils.Utils; +import org.jetbrains.annotations.NotNull; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; import static io.supertokens.storage.mysql.QueryExecutorTemplate.execute; import static io.supertokens.storage.mysql.QueryExecutorTemplate.update; @@ -23,49 +26,52 @@ public static String getQueryToCreateOAuthClientTable(Start start) { // @formatter:off return "CREATE TABLE IF NOT EXISTS " + oAuth2ClientTable + " (" + "app_id VARCHAR(64)," - + "client_id VARCHAR(128) NOT NULL," + + "client_id VARCHAR(255) NOT NULL," + + "client_secret TEXT," + + "enable_refresh_token_rotation BOOLEAN NOT NULL," + "is_client_credentials_only BOOLEAN NOT NULL," + " PRIMARY KEY (app_id, client_id)," + " FOREIGN KEY(app_id) REFERENCES " + Config.getConfig(start).getAppsTable() + "(app_id) ON DELETE CASCADE);"; // @formatter:on } - public static String getQueryToCreateOAuthRevokeTable(Start start) { - String oAuth2RevokeTable = Config.getConfig(start).getOAuthRevokeTable(); + public static String getQueryToCreateOAuthSessionsTable(Start start) { + String oAuthSessionsTable = Config.getConfig(start).getOAuthSessionsTable(); // @formatter:off - return "CREATE TABLE IF NOT EXISTS " + oAuth2RevokeTable + " (" + return "CREATE TABLE IF NOT EXISTS " + oAuthSessionsTable + " (" + + "gid VARCHAR(255)," // needed for instrospect. It's much easier to find these records if we have a gid + "app_id VARCHAR(64) DEFAULT 'public'," - + "target_type VARCHAR(16) NOT NULL," - + "target_value VARCHAR(128) NOT NULL," - + "timestamp BIGINT UNSIGNED NOT NULL, " - + "exp BIGINT UNSIGNED NOT NULL," - + "PRIMARY KEY (app_id, target_type, target_value)," - + "FOREIGN KEY(app_id) " - + " REFERENCES " + Config.getConfig(start).getAppsTable() + "(app_id) ON DELETE CASCADE" - + ");"; + + "client_id VARCHAR(255) NOT NULL," + + "session_handle VARCHAR(128)," + + "external_refresh_token VARCHAR(255) UNIQUE," + + "internal_refresh_token VARCHAR(255) UNIQUE," + + "jti TEXT NOT NULL," // comma separated jti list + + "exp BIGINT NOT NULL," + + "PRIMARY KEY (gid)," + + "FOREIGN KEY(app_id, client_id) REFERENCES " + Config.getConfig(start).getOAuthClientsTable() + "(app_id, client_id) ON DELETE CASCADE);"; // @formatter:on } - public static String getQueryToCreateOAuthRevokeTimestampIndex(Start start) { - String oAuth2RevokeTable = Config.getConfig(start).getOAuthRevokeTable(); - return "CREATE INDEX oauth_revoke_timestamp_index ON " - + oAuth2RevokeTable + "(timestamp DESC, app_id DESC);"; + public static String getQueryToCreateOAuthSessionsExpIndex(Start start) { + String oAuth2SessionTable = Config.getConfig(start).getOAuthSessionsTable(); + return "CREATE INDEX IF NOT EXISTS oauth_session_exp_index ON " + + oAuth2SessionTable + "(exp DESC);"; } - public static String getQueryToCreateOAuthRevokeExpIndex(Start start) { - String oAuth2RevokeTable = Config.getConfig(start).getOAuthRevokeTable(); - return "CREATE INDEX oauth_revoke_exp_index ON " - + oAuth2RevokeTable + "(exp DESC);"; + public static String getQueryToCreateOAuthSessionsExternalRefreshTokenIndex(Start start) { + String oAuth2SessionTable = Config.getConfig(start).getOAuthSessionsTable(); + return "CREATE INDEX IF NOT EXISTS oauth_session_external_refresh_token_index ON " + + oAuth2SessionTable + "(app_id, external_refresh_token DESC);"; } public static String getQueryToCreateOAuthM2MTokensTable(Start start) { String oAuth2M2MTokensTable = Config.getConfig(start).getOAuthM2MTokensTable(); // @formatter:off - return "CREATE TABLE " + oAuth2M2MTokensTable + " (" + return "CREATE TABLE IF NOT EXISTS " + oAuth2M2MTokensTable + " (" + "app_id VARCHAR(64) DEFAULT 'public'," - + "client_id VARCHAR(128) NOT NULL," - + "iat BIGINT UNSIGNED NOT NULL," - + "exp BIGINT UNSIGNED NOT NULL," + + "client_id VARCHAR(255) NOT NULL," + + "iat BIGINT NOT NULL," + + "exp BIGINT NOT NULL," + "PRIMARY KEY (app_id, client_id, iat)," + "FOREIGN KEY(app_id, client_id)" + " REFERENCES " + Config.getConfig(start).getOAuthClientsTable() + "(app_id, client_id) ON DELETE CASCADE" @@ -75,13 +81,13 @@ public static String getQueryToCreateOAuthM2MTokensTable(Start start) { public static String getQueryToCreateOAuthM2MTokenIatIndex(Start start) { String oAuth2M2MTokensTable = Config.getConfig(start).getOAuthM2MTokensTable(); - return "CREATE INDEX oauth_m2m_token_iat_index ON " + return "CREATE INDEX IF NOT EXISTS oauth_m2m_token_iat_index ON " + oAuth2M2MTokensTable + "(iat DESC, app_id DESC);"; } public static String getQueryToCreateOAuthM2MTokenExpIndex(Start start) { String oAuth2M2MTokensTable = Config.getConfig(start).getOAuthM2MTokensTable(); - return "CREATE INDEX oauth_m2m_token_exp_index ON " + return "CREATE INDEX IF NOT EXISTS oauth_m2m_token_exp_index ON " + oAuth2M2MTokensTable + "(exp DESC);"; } @@ -91,11 +97,11 @@ public static String getQueryToCreateOAuthLogoutChallengesTable(Start start) { return "CREATE TABLE IF NOT EXISTS " + oAuth2LogoutChallengesTable + " (" + "app_id VARCHAR(64) DEFAULT 'public'," + "challenge VARCHAR(128) NOT NULL," - + "client_id VARCHAR(128) NOT NULL," + + "client_id VARCHAR(255) NOT NULL," + "post_logout_redirect_uri VARCHAR(1024)," + "session_handle VARCHAR(128)," + "state VARCHAR(128)," - + "time_created BIGINT UNSIGNED NOT NULL," + + "time_created BIGINT NOT NULL," + "PRIMARY KEY (app_id, challenge)," + "FOREIGN KEY(app_id, client_id)" + " REFERENCES " + Config.getConfig(start).getOAuthClientsTable() + "(app_id, client_id) ON DELETE CASCADE" @@ -105,47 +111,92 @@ public static String getQueryToCreateOAuthLogoutChallengesTable(Start start) { public static String getQueryToCreateOAuthLogoutChallengesTimeCreatedIndex(Start start) { String oAuth2LogoutChallengesTable = Config.getConfig(start).getOAuthLogoutChallengesTable(); - return "CREATE INDEX oauth_logout_challenges_time_created_index ON " - + oAuth2LogoutChallengesTable + "(time_created ASC, app_id ASC);"; + return "CREATE INDEX IF NOT EXISTS oauth_logout_challenges_time_created_index ON " + + oAuth2LogoutChallengesTable + "(time_created DESC);"; } - public static boolean doesOAuthClientIdExist(Start start, String clientId, AppIdentifier appIdentifier) + public static OAuthClient getOAuthClientById(Start start, String clientId, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { - String QUERY = "SELECT app_id FROM " + Config.getConfig(start).getOAuthClientsTable() + - " WHERE client_id = ? AND app_id = ?"; + String QUERY = "SELECT client_secret, is_client_credentials_only, enable_refresh_token_rotation FROM " + Config.getConfig(start).getOAuthClientsTable() + + " WHERE client_id = ? AND app_id = ?"; return execute(start, QUERY, pst -> { pst.setString(1, clientId); pst.setString(2, appIdentifier.getAppId()); - }, ResultSet::next); + }, (result) -> { + if (result.next()) { + return new OAuthClient(clientId, result.getString("client_secret"), result.getBoolean("is_client_credentials_only"), result.getBoolean("enable_refresh_token_rotation")); + } + return null; + }); + } + + public static void createOrUpdateOAuthSession(Start start, AppIdentifier appIdentifier, @NotNull String gid, @NotNull String clientId, + String externalRefreshToken, String internalRefreshToken, String sessionHandle, + List jtis, long exp) + throws SQLException, StorageQueryException { + String QUERY = "INSERT INTO " + Config.getConfig(start).getOAuthSessionsTable() + + " (gid, client_id, app_id, external_refresh_token, internal_refresh_token, session_handle, jti, exp) VALUES (?, ?, ?, ?, ?, ?, ?, ?) " + + "ON DUPLICATE KEY UPDATE external_refresh_token = ?, internal_refresh_token = ?, " + + "session_handle = ? , jti = CONCAT(jti, ',' , ?), exp = ?"; + update(start, QUERY, pst -> { + String jtiDbValue = jtis == null ? null : String.join(",", jtis); + + pst.setString(1, gid); + pst.setString(2, clientId); + pst.setString(3, appIdentifier.getAppId()); + pst.setString(4, externalRefreshToken); + pst.setString(5, internalRefreshToken); + pst.setString(6, sessionHandle); + pst.setString(7, jtiDbValue); + pst.setLong(8, exp); + + pst.setString(9, externalRefreshToken); + pst.setString(10, internalRefreshToken); + pst.setString(11, sessionHandle); + pst.setString(12, jtiDbValue); + pst.setLong(13, exp); + }); } - public static List listOAuthClients(Start start, AppIdentifier appIdentifier) + public static List getOAuthClients(Start start, AppIdentifier appIdentifier, List clientIds) throws SQLException, StorageQueryException { - String QUERY = "SELECT client_id FROM " + Config.getConfig(start).getOAuthClientsTable() + - " WHERE app_id = ?"; + if(clientIds.isEmpty()){ + return Collections.emptyList(); + } + String QUERY = "SELECT * FROM " + Config.getConfig(start).getOAuthClientsTable() + + " WHERE app_id = ? AND client_id IN (" + + Utils.generateCommaSeperatedQuestionMarks(clientIds.size()) + + ")"; return execute(start, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); + for (int i = 0; i < clientIds.size(); i++) { + pst.setString(i + 2, clientIds.get(i)); + } }, (result) -> { - List res = new ArrayList<>(); + List res = new ArrayList<>(); while (result.next()) { - res.add(result.getString("client_id")); + res.add(new OAuthClient(result.getString("client_id"), result.getString("client_secret"), result.getBoolean("is_client_credentials_only"), result.getBoolean("enable_refresh_token_rotation"))); } return res; }); } - public static void addOrUpdateOauthClient(Start start, AppIdentifier appIdentifier, String clientId, - boolean isClientCredentialsOnly) + public static void addOrUpdateOauthClient(Start start, AppIdentifier appIdentifier, String clientId, String clientSecret, + boolean isClientCredentialsOnly, boolean enableRefreshTokenRotation) throws SQLException, StorageQueryException { String INSERT = "INSERT INTO " + Config.getConfig(start).getOAuthClientsTable() - + "(app_id, client_id, is_client_credentials_only) VALUES(?, ?, ?) " - + "ON DUPLICATE KEY UPDATE is_client_credentials_only = ?"; + + "(app_id, client_id, client_secret, is_client_credentials_only, enable_refresh_token_rotation) VALUES(?, ?, ?, ?, ?) " + + "ON DUPLICATE KEY UPDATE client_secret = ?, is_client_credentials_only = ?, enable_refresh_token_rotation = ?"; update(start, INSERT, pst -> { pst.setString(1, appIdentifier.getAppId()); pst.setString(2, clientId); - pst.setBoolean(3, isClientCredentialsOnly); + pst.setString(3, clientSecret); pst.setBoolean(4, isClientCredentialsOnly); + pst.setBoolean(5, enableRefreshTokenRotation); + pst.setString(6, clientSecret); + pst.setBoolean(7, isClientCredentialsOnly); + pst.setBoolean(8, enableRefreshTokenRotation); }); } @@ -160,55 +211,55 @@ public static boolean deleteOAuthClient(Start start, String clientId, AppIdentif return numberOfRow > 0; } - public static void revokeOAuthTokensBasedOnTargetFields(Start start, AppIdentifier appIdentifier, OAuthRevokeTargetType targetType, String targetValue, long exp) + public static boolean deleteOAuthSessionByGID(Start start, AppIdentifier appIdentifier, String gid) throws SQLException, StorageQueryException { - String INSERT = "INSERT INTO " + Config.getConfig(start).getOAuthRevokeTable() - + "(app_id, target_type, target_value, timestamp, exp) VALUES (?, ?, ?, ?, ?) " - + "ON DUPLICATE KEY UPDATE timestamp = ?, exp = ?"; + String DELETE = "DELETE FROM " + Config.getConfig(start).getOAuthSessionsTable() + + " WHERE gid = ? and app_id = ?;"; + int numberOfRows = update(start, DELETE, pst -> { + pst.setString(1, gid); + pst.setString(2, appIdentifier.getAppId()); + }); + return numberOfRows > 0; + } - long currentTime = System.currentTimeMillis() / 1000; - update(start, INSERT, pst -> { + public static boolean deleteOAuthSessionByClientId(Start start, AppIdentifier appIdentifier, String clientId) + throws SQLException, StorageQueryException { + String DELETE = "DELETE FROM " + Config.getConfig(start).getOAuthSessionsTable() + + " WHERE app_id = ? and client_id = ?;"; + int numberOfRows = update(start, DELETE, pst -> { pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, targetType.getValue()); - pst.setString(3, targetValue); - pst.setLong(4, currentTime); - pst.setLong(5, exp); - pst.setLong(6, currentTime); - pst.setLong(7, exp); + pst.setString(2, clientId); }); + return numberOfRows > 0; } - public static boolean isOAuthTokenRevokedBasedOnTargetFields(Start start, AppIdentifier appIdentifier, OAuthRevokeTargetType[] targetTypes, String[] targetValues, long issuedAt) + public static boolean deleteOAuthSessionBySessionHandle(Start start, AppIdentifier appIdentifier, String sessionHandle) throws SQLException, StorageQueryException { - String QUERY = "SELECT app_id FROM " + Config.getConfig(start).getOAuthRevokeTable() + - " WHERE app_id = ? AND timestamp > ? AND ("; - - for (int i = 0; i < targetTypes.length; i++) { - QUERY += "(target_type = ? AND target_value = ?)"; - - if (i < targetTypes.length - 1) { - QUERY += " OR "; - } - } - - QUERY += ")"; - - return execute(start, QUERY, pst -> { + String DELETE = "DELETE FROM " + Config.getConfig(start).getOAuthSessionsTable() + + " WHERE app_id = ? and session_handle = ?"; + int numberOfRows = update(start, DELETE, pst -> { pst.setString(1, appIdentifier.getAppId()); - pst.setLong(2, issuedAt); - - int index = 3; - for (int i = 0; i < targetTypes.length; i++) { - pst.setString(index, targetTypes[i].getValue()); - index++; - pst.setString(index, targetValues[i]); - index++; - } - }, ResultSet::next); + pst.setString(2, sessionHandle); + }); + return numberOfRows > 0; + } + + public static boolean deleteJTIFromOAuthSession(Start start, AppIdentifier appIdentifier, String gid, String jti) + throws SQLException, StorageQueryException { + //jti is a comma separated list. When deleting a jti, just have to delete from the list + String DELETE = "UPDATE " + Config.getConfig(start).getOAuthSessionsTable() + + " SET jti = REPLACE(jti, ?, '')" // deletion means replacing the jti with empty char + + " WHERE app_id = ? and gid = ?"; + int numberOfRows = update(start, DELETE, pst -> { + pst.setString(1, jti); + pst.setString(2, appIdentifier.getAppId()); + pst.setString(3, gid); + }); + return numberOfRows > 0; } public static int countTotalNumberOfClients(Start start, AppIdentifier appIdentifier, - boolean filterByClientCredentialsOnly) throws SQLException, StorageQueryException { + boolean filterByClientCredentialsOnly) throws SQLException, StorageQueryException { if (filterByClientCredentialsOnly) { String QUERY = "SELECT COUNT(*) as c FROM " + Config.getConfig(start).getOAuthClientsTable() + " WHERE app_id = ? AND is_client_credentials_only = ?"; @@ -277,32 +328,8 @@ public static void addOAuthM2MTokenForStats(Start start, AppIdentifier appIdenti }); } - public static void cleanUpExpiredAndRevokedOAuthTokensList(Start start) throws SQLException, StorageQueryException { - { - // delete expired M2M tokens - String QUERY = "DELETE FROM " + Config.getConfig(start).getOAuthM2MTokensTable() + - " WHERE exp < ?"; - - long timestamp = System.currentTimeMillis() / 1000 - 3600 * 24 * 31; // expired 31 days ago - update(start, QUERY, pst -> { - pst.setLong(1, timestamp); - }); - } - - { - // delete expired revoked tokens - String QUERY = "DELETE FROM " + Config.getConfig(start).getOAuthRevokeTable() + - " WHERE exp < ?"; - - long timestamp = System.currentTimeMillis() / 1000 - 3600 * 24 * 31; // expired 31 days ago - update(start, QUERY, pst -> { - pst.setLong(1, timestamp); - }); - } - } - public static void addOAuthLogoutChallenge(Start start, AppIdentifier appIdentifier, String challenge, String clientId, - String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) throws SQLException, StorageQueryException { + String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) throws SQLException, StorageQueryException { String QUERY = "INSERT INTO " + Config.getConfig(start).getOAuthLogoutChallengesTable() + " (app_id, challenge, client_id, post_logout_redirect_uri, session_handle, state, time_created) VALUES (?, ?, ?, ?, ?, ?, ?)"; update(start, QUERY, pst -> { @@ -320,19 +347,19 @@ public static OAuthLogoutChallenge getOAuthLogoutChallenge(Start start, AppIdent String QUERY = "SELECT challenge, client_id, post_logout_redirect_uri, session_handle, state, time_created FROM " + Config.getConfig(start).getOAuthLogoutChallengesTable() + " WHERE app_id = ? AND challenge = ?"; - + return execute(start, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); pst.setString(2, challenge); }, result -> { if (result.next()) { return new OAuthLogoutChallenge( - result.getString("challenge"), - result.getString("client_id"), - result.getString("post_logout_redirect_uri"), - result.getString("session_handle"), - result.getString("state"), - result.getLong("time_created") + result.getString("challenge"), + result.getString("client_id"), + result.getString("post_logout_redirect_uri"), + result.getString("session_handle"), + result.getString("state"), + result.getLong("time_created") ); } return null; @@ -355,4 +382,70 @@ public static void deleteOAuthLogoutChallengesBefore(Start start, long time) thr pst.setLong(1, time); }); } + + public static String getRefreshTokenMapping(Start start, AppIdentifier appIdentifier, String externalRefreshToken) throws SQLException, StorageQueryException { + String QUERY = "SELECT internal_refresh_token FROM " + Config.getConfig(start).getOAuthSessionsTable() + + " WHERE app_id = ? AND external_refresh_token = ?"; + return execute(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, externalRefreshToken); + }, result -> { + if (result.next()) { + return result.getString("internal_refresh_token"); + } + return null; + }); + } + + public static void deleteExpiredOAuthSessions(Start start, long exp) throws SQLException, StorageQueryException { + // delete expired M2M tokens + String QUERY = "DELETE FROM " + Config.getConfig(start).getOAuthSessionsTable() + + " WHERE exp < ?"; + + update(start, QUERY, pst -> { + pst.setLong(1, exp); + }); + } + + public static void deleteExpiredOAuthM2MTokens(Start start, long exp) throws SQLException, StorageQueryException { + // delete expired M2M tokens + String QUERY = "DELETE FROM " + Config.getConfig(start).getOAuthM2MTokensTable() + + " WHERE exp < ?"; + update(start, QUERY, pst -> { + pst.setLong(1, exp); + }); + } + + public static boolean isOAuthSessionExistsByJTI(Start start, AppIdentifier appIdentifier, String gid, String jti) + throws SQLException, StorageQueryException { + String SELECT = "SELECT jti FROM " + Config.getConfig(start).getOAuthSessionsTable() + + " WHERE app_id = ? and gid = ?;"; + return execute(start, SELECT, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, gid); + }, result -> { + if(result.next()){ + List jtis = Arrays.stream(result.getString(1).split(",")).filter(s -> !s.isEmpty()).collect( + Collectors.toList()); + return jtis.contains(jti); + } + return false; + }); + } + + public static boolean isOAuthSessionExistsByGID(Start start, AppIdentifier appIdentifier, String gid) + throws SQLException, StorageQueryException { + String SELECT = "SELECT count(*) FROM " + Config.getConfig(start).getOAuthSessionsTable() + + " WHERE app_id = ? and gid = ?;"; + return execute(start, SELECT, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, gid); + }, result -> { + if(result.next()){ + return result.getInt(1) > 0; + } + return false; + }); + } + }