-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bugfix/apikey header capitalization #447
Changes from 5 commits
eb6c121
4cf28d3
dfd8a89
626d730
b900ed9
5fb14da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ | |
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.TimeZone; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.logging.Level; | ||
|
||
import javax.sql.DataSource; | ||
|
@@ -51,7 +52,7 @@ 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 final String RETRIEVE_GROUPS_OF_USER; | ||
private static String RETRIEVE_GROUPS_OF_USER = null; | ||
|
||
private static final String SET_API_USER_DIRECT = "begin " | ||
+ "cwms_env.set_session_user_direct(upper(?));" | ||
|
@@ -69,28 +70,31 @@ public class AuthDao extends Dao<DataApiPrincipal>{ | |
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 boolean hasCwmsEnvMultiOfficeAuthFix; | ||
private String connectionUser; | ||
private String defaultOffice; | ||
private static boolean hasCwmsEnvMultiOfficeAuthFix = false; | ||
private static String connectionUser = null; | ||
private static String defaultOffice = null; | ||
|
||
public AuthDao(DSLContext dsl, String defaultOffice) { | ||
super(dsl); | ||
if (getDbVersion() < Dao.CWMS_23_03_16) { | ||
throw new RuntimeException(SCHEMA_TOO_OLD); | ||
} | ||
this.defaultOffice = defaultOffice; | ||
try { | ||
connectionUser = dsl.connectionResult(c->c.getMetaData().getUserName()); | ||
dsl.execute("BEGIN cwms_env.set_session_user_direct(?,?)", connectionUser,defaultOffice); | ||
hasCwmsEnvMultiOfficeAuthFix = true; | ||
} catch (DataAccessException ex) { | ||
if( ex.getLocalizedMessage() | ||
.toLowerCase() | ||
.contains("wrong number or types of arguments in call")) { | ||
hasCwmsEnvMultiOfficeAuthFix = false; | ||
|
||
if (AuthDao.defaultOffice == null) { | ||
AuthDao.defaultOffice = defaultOffice; | ||
try { | ||
connectionUser = dsl.connectionResult(c->c.getMetaData().getUserName()); | ||
dsl.execute("BEGIN cwms_env.set_session_user_direct(?,?)", connectionUser,defaultOffice); | ||
hasCwmsEnvMultiOfficeAuthFix = true; | ||
} catch (DataAccessException ex) { | ||
if( ex.getLocalizedMessage() | ||
.toLowerCase() | ||
.contains("wrong number or types of arguments in call")) { | ||
hasCwmsEnvMultiOfficeAuthFix = false; | ||
} | ||
} | ||
AuthDao.RETRIEVE_GROUPS_OF_USER = ResourceHelper.getResourceAsString("/cwms/data/sql/user_groups.sql",this.getClass()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason to do this here vs at the variable declaration? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm quite sure there was when I initial did the work. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, that's not a bad idea. Let me chew on that one a bit. It is supposed to be usable by all the possible Auth mechanisms. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, that response was to the comment above but I did go ahead and just do this. |
||
} | ||
this.RETRIEVE_GROUPS_OF_USER = ResourceHelper.getResourceAsString("/cwms/data/sql/user_groups.sql",this.getClass()); | ||
} | ||
|
||
@Override | ||
|
@@ -140,21 +144,30 @@ private void setSessionForAuthCheck(Connection conn) throws SQLException { | |
} | ||
|
||
private String checkKey(String key) throws CwmsAuthException { | ||
return dsl.connectionResult(c-> { | ||
setSessionForAuthCheck(c); | ||
try (PreparedStatement checkForKey = c.prepareStatement(CHECK_API_KEY);) { | ||
checkForKey.setString(1,key); | ||
try (ResultSet rs = checkForKey.executeQuery()) { | ||
if (rs.next()) { | ||
return rs.getString(1); | ||
} else { | ||
throw new CwmsAuthException("No user for key"); | ||
try { | ||
return dsl.connectionResult(c-> { | ||
setSessionForAuthCheck(c); | ||
try (PreparedStatement checkForKey = c.prepareStatement(CHECK_API_KEY);) { | ||
checkForKey.setString(1,key); | ||
try (ResultSet rs = checkForKey.executeQuery()) { | ||
if (rs.next()) { | ||
return rs.getString(1); | ||
} else { | ||
throw new CwmsAuthException("No user for key"); | ||
} | ||
} | ||
} catch (SQLException ex) { | ||
throw new CwmsAuthException("Failed API key check",ex); | ||
} | ||
} catch (SQLException ex) { | ||
throw new CwmsAuthException("Failed API key check",ex); | ||
}); | ||
} catch (DataAccessException ex) { | ||
Throwable t = ex.getCause(); | ||
if (t instanceof CwmsAuthException) { | ||
throw (CwmsAuthException)t; | ||
} else { | ||
throw ex; | ||
} | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -193,7 +206,9 @@ private Set<RouteRole> getRolesForUser(String user) { | |
public static void prepareContextWithUser(Context ctx, DataApiPrincipal p) throws SQLException { | ||
Objects.requireNonNull(ctx, "A valid Javalin Context must be provided to this call."); | ||
Objects.requireNonNull(p, "A valid data api principal must be provided to this call."); | ||
logger.atInfo().log("Validated Api Key for user=%s", p.getName()); | ||
logger.atInfo() | ||
.atMostEvery(5,TimeUnit.SECONDS) | ||
.log("Validated Api Key for user=%s", p.getName()); | ||
DataSource dataSource = ctx.attribute(ApiServlet.DATA_SOURCE); | ||
ConnectionPreparer userPreparer = new DirectUserPreparer(p.getName()); | ||
ctx.attribute(DATA_API_PRINCIPAL,p); | ||
|
@@ -377,4 +392,12 @@ public DataApiPrincipal getDataApiPrincipal(Context ctx) | |
{ | ||
return ctx.attribute(DATA_API_PRINCIPAL); | ||
} | ||
|
||
/** | ||
* Used to avoid constant instancing of the AuthDao objects | ||
* @param dslContext | ||
*/ | ||
public void resetContext(DSLContext dslContext) { | ||
this.dsl = dslContext; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making these static and conditionally setting them in the constructor weirds me out. I can't say exactly why but it feels like asking for trouble. Like if the first request made is for an un-checked get end-point and the request includes office_id will it cause the static value to get assigned to whatever the first request provided? At the very minimum it seems like assigning to AuthDao.defaultOffice shouldn't happen if the set_session_user_direct failed - right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This always uses the office id of the context "e.g. cwms -> hq, spk -> spk" not the context of data.
For the 2nd part I think it just won't get used, but I don't think the additional logic is required just for that state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the short version is that I'm just trying to avoid creating a new instance of this object on every request since it will be used on a LOT of requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok. the part about the context makes more sense. Should AuthDao have a static getInstance() and just use it singleton style?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered the use of a singleton, that's going to need more investigation. Since the clientInfo is different for every request i needs the new context on each run so I think for now we do new to just run new on every request since their could be multiple threads. Or synchronize on the object which seems like a terrible idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, after realizing it needs the javalin Context information of the current request so that setClientInfo is correct for tracing instead of a pure singleton I made it a singleton per thread.