Skip to content

Commit

Permalink
Setting the session timezone to UTC on every connection
Browse files Browse the repository at this point in the history
  • Loading branch information
rma-rripken committed May 28, 2024
1 parent 701d7e6 commit d3c0ad7
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 25 deletions.
69 changes: 44 additions & 25 deletions cwms-data-api/src/main/java/cwms/cda/data/dao/AuthDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import cwms.cda.datasource.DelegatingConnectionPreparer;
import cwms.cda.datasource.DirectUserPreparer;
import cwms.cda.datasource.SessionOfficePreparer;
import cwms.cda.datasource.SessionTimeZonePreparer;
import cwms.cda.helpers.ResourceHelper;
import cwms.cda.security.CwmsAuthException;
import cwms.cda.security.DataApiPrincipal;
Expand Down Expand Up @@ -45,7 +46,8 @@ public class AuthDao extends Dao<DataApiPrincipal> {
+ "23.03.16 or later to handle authorization operations.";
public static final String DATA_API_PRINCIPAL = "DataApiPrincipal";
// At this level we just care that the user has permissions in *any* office
private static final String RETRIEVE_GROUPS_OF_USER = ResourceHelper.getResourceAsString("/cwms/data/sql/user_groups.sql",AuthDao.class);
private static final String RETRIEVE_GROUPS_OF_USER =
ResourceHelper.getResourceAsString("/cwms/data/sql/user_groups.sql", AuthDao.class);

private static final String SET_API_USER_DIRECT = "begin "
+ "cwms_env.set_session_user_direct(upper(?));"
Expand All @@ -60,10 +62,14 @@ public class AuthDao extends Dao<DataApiPrincipal> {
private static final String USER_FOR_EDIPI =
"select userid from cwms_20.at_sec_cwms_users where edipi = ?";

public static final String CREATE_API_KEY = "insert into cwms_20.at_api_keys(userid,key_name,apikey,created,expires) values(UPPER(?),?,?,?,?)";
public static final String REMOVE_API_KEY = "delete from cwms_20.at_api_keys where UPPER(userid) = UPPER(?) and key_name = ?";
public static final String LIST_KEYS = "select userid,key_name,created,expires from cwms_20.at_api_keys where UPPER(userid) = UPPER(?) order by created desc";
public static final String GET_SINGLE_KEY = "select userid,key_name,created,expires from cwms_20.at_api_keys where UPPER(userid) = UPPER(?) and key_name = ?";
public static final String CREATE_API_KEY = "insert into cwms_20.at_api_keys"
+ "(userid, key_name, apikey, created, expires) values(UPPER(?),?,?,?,?)";
public static final String REMOVE_API_KEY = "delete from cwms_20.at_api_keys "
+ "where UPPER(userid) = UPPER(?) and key_name = ?";
public static final String LIST_KEYS = "select userid, key_name, created, expires "
+ "from cwms_20.at_api_keys where UPPER(userid) = UPPER(?) order by created desc";
public static final String GET_SINGLE_KEY = "select userid, key_name, created, expires "
+ "from cwms_20.at_api_keys where UPPER(userid) = UPPER(?) and key_name = ?";
public static final String ONLY_OWN_KEY_MESSAGE = "You may not create API keys for any user other than your own.";

private static boolean hasCwmsEnvMultiOfficeAuthFix = false;
Expand Down Expand Up @@ -103,9 +109,9 @@ private AuthDao(DSLContext dsl, String defaultOffice) {
* Get Appropriate instance of the AuthDAO. Setup with the given DSLContext.
* The instance of AuthDAO returned is local to a given servicing thread.
*
* @param dsl
* @param dsl the DSLContext to use
* @param defaultOffice can be null
* @return
* @return an instance of the AuthDAO
*/
public static AuthDao getInstance(DSLContext dsl, String defaultOffice) {
AuthDao dao = instance.get();
Expand All @@ -121,8 +127,8 @@ public static AuthDao getInstance(DSLContext dsl, String defaultOffice) {
/**
* Used in sections of code that will always be called after the default office is set
* but that still need to interact with this DAO.
* @param dsl
* @return
* @param dsl the DSLContext to use
* @return an instance of the AuthDAO
*/
public static AuthDao getInstance(DSLContext dsl) {
return getInstance(dsl, null);
Expand All @@ -135,7 +141,7 @@ public List<DataApiPrincipal> getAll(String limitToOffice) {

/**
* Reserved for future use, get user principal by presented unique name and office.
* (Also required by Dao<dataApiPrincipal>)
* (Also required by Dao&lt;DataApiPrincipal&gt;)
*/
@Override
public Optional<DataApiPrincipal> getByUniqueName(String uniqueName, String limitToOffice) throws CwmsAuthException {
Expand All @@ -157,7 +163,7 @@ public DataApiPrincipal getByApiKey(String apikey) throws CwmsAuthException {
/**
* Setup session environment so we can query the required tables.
* @param conn the connection to setup.
* @throws SQLException
* @throws SQLException if there is an issue setting up the session.
*/
private void setSessionForAuthCheck(Connection conn) throws SQLException {
if (hasCwmsEnvMultiOfficeAuthFix) {
Expand Down Expand Up @@ -205,7 +211,7 @@ private String checkKey(String key) throws CwmsAuthException {
*
* @param edipi the edipi to look up.
* @return the username for the given edipi.
* @throws CwmsAuthException
* @throws CwmsAuthException if the user is not in the database.
*/
private String userForEdipi(long edipi) throws CwmsAuthException {
try {
Expand Down Expand Up @@ -236,7 +242,8 @@ private String userForEdipi(long edipi) throws CwmsAuthException {
/**
* Build a DataApiPrincipal from a given EDIPI value.
* @param edipi the Edipi value to look up.
* @return
* @return the DataApiPrincipal object for the given edipi.
* @throws CwmsAuthException if the user is not in the database.
*/
public DataApiPrincipal getPrincipalFromEdipi(Long edipi) throws CwmsAuthException {
String username = userForEdipi(edipi);
Expand Down Expand Up @@ -302,6 +309,7 @@ public static void prepareContextWithUser(Context ctx, DataApiPrincipal p) {
* @param ctx the context to check
* @param p the principal to check
* @param routeRoles the required roles
* @throws CwmsAuthException if the user is not authorized
*/
public static void isAuthorized(Context ctx, DataApiPrincipal p, Set<RouteRole> routeRoles) throws CwmsAuthException {
if (routeRoles == null || routeRoles.isEmpty()) {
Expand All @@ -326,9 +334,10 @@ public static void isAuthorized(Context ctx, DataApiPrincipal p, Set<RouteRole>
*/
public void prepareGuestContext(Context ctx) {
DataSource dataSource = ctx.attribute(ApiServlet.DATA_SOURCE);
SessionTimeZonePreparer utcPrep = new SessionTimeZonePreparer();
ConnectionPreparer officePreparer = new SessionOfficePreparer(defaultOffice);
ConnectionPreparer userPreparer = new DirectUserPreparer(connectionUser);
ConnectionPreparer guestPreparer = new DelegatingConnectionPreparer(userPreparer,officePreparer);
ConnectionPreparer guestPreparer = new DelegatingConnectionPreparer(utcPrep, userPreparer, officePreparer);

if (dataSource instanceof ConnectionPreparingDataSource) {
ConnectionPreparingDataSource cpDs = (ConnectionPreparingDataSource)dataSource;
Expand Down Expand Up @@ -358,6 +367,7 @@ private static String getFailMessage(@NotNull Context ctx,
* @param p Principal object, to get the username
* @param sourceData new key is created based on userId, keyname and expires info from this key.
* @return The created ApiKey
* @throws CwmsAuthException if the key cannot be created.
*/
public ApiKey createApiKey(DataApiPrincipal p, ApiKey sourceData) throws CwmsAuthException {

Expand All @@ -371,16 +381,24 @@ public ApiKey createApiKey(DataApiPrincipal p, ApiKey sourceData) throws CwmsAut
.limit(256)
.collect(StringBuilder::new,StringBuilder::appendCodePoint, StringBuilder::append)
.toString();
final ApiKey newKey = new ApiKey(sourceData.getUserId().toUpperCase(),sourceData.getKeyName(),key,ZonedDateTime.now(ZoneId.of("UTC")),sourceData.getExpires());
final ApiKey newKey = new ApiKey(
sourceData.getUserId().toUpperCase(),
sourceData.getKeyName(),
key,
ZonedDateTime.now(ZoneId.of("UTC")),
sourceData.getExpires()
);
dsl.connection(c -> {
setSessionForAuthCheck(c);
try (PreparedStatement createKey = c.prepareStatement(CREATE_API_KEY)) {
createKey.setString(1,newKey.getUserId());
createKey.setString(2,newKey.getKeyName());
createKey.setString(3,newKey.getApiKey());
createKey.setDate(4,new Date(newKey.getCreated().toInstant().toEpochMilli()),Calendar.getInstance(TimeZone.getTimeZone("UTC")));
createKey.setString(1, newKey.getUserId());
createKey.setString(2, newKey.getKeyName());
createKey.setString(3, newKey.getApiKey());
createKey.setDate(4, new Date(newKey.getCreated().toInstant().toEpochMilli()),
Calendar.getInstance(TimeZone.getTimeZone("UTC")));
if (newKey.getExpires() != null) {
createKey.setDate(5,new Date(newKey.getExpires().toInstant().toEpochMilli()),Calendar.getInstance(TimeZone.getTimeZone("UTC")));
createKey.setDate(5, new Date(newKey.getExpires().toInstant().toEpochMilli()),
Calendar.getInstance(TimeZone.getTimeZone("UTC")));
} else {
createKey.setDate(5,null);
}
Expand All @@ -389,14 +407,15 @@ public ApiKey createApiKey(DataApiPrincipal p, ApiKey sourceData) throws CwmsAut
});
return newKey;
} catch (NoSuchAlgorithmException ex) {
throw new CwmsAuthException("Unable to generate appropriate key.", ex, HttpCode.INTERNAL_SERVER_ERROR.getStatus());
throw new CwmsAuthException("Unable to generate appropriate key.", ex,
HttpCode.INTERNAL_SERVER_ERROR.getStatus());
}


}

/**
* Return all API Keys for a given user
* Return all API Keys for a given user.
* @param p User for which we want the keys
* @return List of all the keys, with the actual key removed (only user,name,created, and expires)
*/
Expand All @@ -423,7 +442,7 @@ public ApiKey apiKeyForUser(DataApiPrincipal p, String keyName) {
singleKey.setString(1,p.getName());
singleKey.setString(2,keyName);
try (ResultSet rs = singleKey.executeQuery()) {
if(rs.next()) {
if (rs.next()) {
return rs2ApiKey(rs);
} else {
return null;
Expand All @@ -442,7 +461,7 @@ private static ApiKey rs2ApiKey(ResultSet rs) throws SQLException {
}

/**
* Remove a given API Key
* Remove a given API Key.
* @param p User principal to narrow and limit request
* @param keyName name of the key to remove
*/
Expand All @@ -463,7 +482,7 @@ public DataApiPrincipal getDataApiPrincipal(Context ctx) {
}

/**
* Used to avoid constant instancing of the AuthDao objects
* Used to avoid constant instancing of the AuthDao objects.
* @param dslContext The jOOQ DSLContext
*/
public void resetContext(DSLContext dslContext) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package cwms.cda.datasource;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.annotation.Nullable;

public class SessionTimeZonePreparer implements ConnectionPreparer {

public SessionTimeZonePreparer() {

}

private static @Nullable String getSessionTimeZone(Connection connection) throws SQLException {
String dbTimeZone = null;
String sql = "SELECT sessiontimezone FROM dual";
try (Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql)) {
if (resultSet.next()) {
dbTimeZone = resultSet.getString(1);
}
}
return dbTimeZone;
}

private static void setSessionTimeZoneUtc(Connection connection) throws SQLException {
String sql = "ALTER SESSION SET TIME_ZONE = 'UTC'";
try (CallableStatement statement = connection.prepareCall(sql)) {
statement.execute();
}
}

@Override
public Connection prepare(Connection conn) throws SQLException {
setSessionTimeZoneUtc(conn);
return conn;
}

}

0 comments on commit d3c0ad7

Please sign in to comment.