From ffff175625c2392ba45044b0d61694f55ad49348 Mon Sep 17 00:00:00 2001 From: Ryan Doherty Date: Wed, 31 Jan 2024 21:39:07 -0500 Subject: [PATCH] Changes to support bearer tokens and remove accountdb access from WDK --- .../conifer/templates/WDK/model-config.xml.j2 | 11 +- .../filter/CheckLoginFilterShared.java | 185 ------------------ .../java/org/gusdb/wdk/model/WdkModel.java | 3 +- .../model/config/ModelConfigAccountDB.java | 13 -- .../wdk/model/config/ModelConfigParser.java | 2 - .../gusdb/wdk/model/user/BearerTokenUser.java | 51 +++++ .../gusdb/wdk/model/user/RegisteredUser.java | 28 --- .../wdk/model/user/UnregisteredUser.java | 39 ---- .../java/org/gusdb/wdk/model/user/User.java | 176 +++++++++++------ .../org/gusdb/wdk/model/user/UserCache.java | 11 +- .../wdk/model/user/UserCreationScript.java | 165 ---------------- .../org/gusdb/wdk/model/user/UserFactory.java | 75 +++---- .../gusdb/wdk/model/user/WdkUserProperty.java | 32 +++ .../wdk/session/WdkOAuthClientWrapper.java | 89 +++------ .../model/user/UserCreationScriptTest.java | 34 ---- .../wdk/service/filter/CheckLoginFilter.java | 111 ++++++++--- .../service/formatter/ProjectFormatter.java | 8 +- .../service/formatter/StrategyFormatter.java | 2 +- .../wdk/service/formatter/UserFormatter.java | 14 +- .../service/service/AbstractWdkService.java | 20 +- .../wdk/service/service/AnswerService.java | 4 +- .../wdk/service/service/QuestionService.java | 12 +- .../wdk/service/service/RecordService.java | 2 +- .../wdk/service/service/SessionService.java | 58 +++--- .../service/TemporaryResultService.java | 6 +- .../service/search/ColumnReporterService.java | 4 +- .../service/service/user/BasketService.java | 4 +- .../service/service/user/ProfileService.java | 12 +- .../service/service/user/StrategyService.java | 4 +- .../service/user/UserDatasetService.java | 2 +- 30 files changed, 433 insertions(+), 744 deletions(-) delete mode 100644 Model/src/main/java/org/gusdb/wdk/controller/filter/CheckLoginFilterShared.java create mode 100644 Model/src/main/java/org/gusdb/wdk/model/user/BearerTokenUser.java delete mode 100644 Model/src/main/java/org/gusdb/wdk/model/user/RegisteredUser.java delete mode 100644 Model/src/main/java/org/gusdb/wdk/model/user/UnregisteredUser.java delete mode 100644 Model/src/main/java/org/gusdb/wdk/model/user/UserCreationScript.java create mode 100644 Model/src/main/java/org/gusdb/wdk/model/user/WdkUserProperty.java delete mode 100644 Model/src/test/java/org/gusdb/wdk/model/user/UserCreationScriptTest.java diff --git a/Model/lib/conifer/roles/conifer/templates/WDK/model-config.xml.j2 b/Model/lib/conifer/roles/conifer/templates/WDK/model-config.xml.j2 index 3cd45abc51..e831a0dcc4 100644 --- a/Model/lib/conifer/roles/conifer/templates/WDK/model-config.xml.j2 +++ b/Model/lib/conifer/roles/conifer/templates/WDK/model-config.xml.j2 @@ -177,16 +177,7 @@ site_vars file: {{ site_vars }} {% if modelconfig_accountDb_driverInitClass is defined -%} driverInitClass="{{ modelconfig_accountDb_driverInitClass }}" {% endif %} - > - - - - - - - - - + /> {% if modelconfig_userDatasetStoreConfig is defined -%} {{ modelconfig_userDatasetStoreConfig|indent }} diff --git a/Model/src/main/java/org/gusdb/wdk/controller/filter/CheckLoginFilterShared.java b/Model/src/main/java/org/gusdb/wdk/controller/filter/CheckLoginFilterShared.java deleted file mode 100644 index 529c9ae58b..0000000000 --- a/Model/src/main/java/org/gusdb/wdk/controller/filter/CheckLoginFilterShared.java +++ /dev/null @@ -1,185 +0,0 @@ -package org.gusdb.wdk.controller.filter; - -import java.util.Optional; - -import org.apache.log4j.Logger; -import org.gusdb.fgputil.Tuples.ThreeTuple; -import org.gusdb.fgputil.events.Events; -import org.gusdb.fgputil.web.CookieBuilder; -import org.gusdb.fgputil.web.LoginCookieFactory; -import org.gusdb.fgputil.web.LoginCookieFactory.LoginCookieParts; -import org.gusdb.fgputil.web.SessionProxy; -import org.gusdb.wdk.events.NewUserEvent; -import org.gusdb.wdk.model.Utilities; -import org.gusdb.wdk.model.WdkModel; -import org.gusdb.wdk.model.WdkRuntimeException; -import org.gusdb.wdk.model.user.UnregisteredUser.UnregisteredUserType; - -import io.prometheus.client.Counter; - -import org.gusdb.wdk.model.user.User; -import org.gusdb.wdk.model.user.UserFactory; - -public class CheckLoginFilterShared { - - private static final Logger LOG = Logger.getLogger(CheckLoginFilterShared.class); - - private static final Counter GUEST_CREATION_COUNTER = Counter.build() - .name("wdk_guest_creation_count") - .help("Number of guest users created by WDK services") - .register(); - - private enum CurrentState { - - REGISTERED_USER_MATCHING_COOKIE, // 1. registered user, matching wdk cookie = no action - REGISTERED_USER_BAD_COOKIE, // 2. registered user, unmatching, invalid, or missing wdk cookie = send expired cookie, new guest - GUEST_USER_COOKIE_PRESENT, // 3. guest user, any wdk cookie = send expired cookie, keep guest - GUEST_USER_COOKIE_MISSING, // 4. guest user, missing wdk cookie = no action - NO_USER_VALID_COOKIE, // 5. no user, valid wdk cookie = log user in, but do not send updated cookie, since doing so is a bug for GBrowse - NO_USER_INVALID_COOKIE, // 6. no user, invalid wdk cookie = send expired cookie, new guest - NO_USER_MISSING_COOKIE; // 7. no user, missing wdk cookie = new guest - - public boolean requiresAction() { - return !equals(REGISTERED_USER_MATCHING_COOKIE) && - !equals(GUEST_USER_COOKIE_MISSING); - } - - public static CurrentState calculateState( - boolean userPresent, - boolean isGuestUser, - boolean cookiePresent, - boolean cookieValid, - boolean cookieMatches) { - return - (userPresent ? - // user is present cases - (isGuestUser ? - // guest user cases - (cookiePresent ? GUEST_USER_COOKIE_PRESENT : GUEST_USER_COOKIE_MISSING) : - // logged user cases - (cookieMatches ? REGISTERED_USER_MATCHING_COOKIE : REGISTERED_USER_BAD_COOKIE)) : - // no user present cases - (cookiePresent ? - // cookie but no user - (cookieValid ? NO_USER_VALID_COOKIE : NO_USER_INVALID_COOKIE) : - // no cookie, no user - NO_USER_MISSING_COOKIE)); - } - } - - private static class StateBundle extends ThreeTuple { - - public StateBundle(CurrentState state, User sessionUser, String cookieEmail) { - super(state, sessionUser, cookieEmail); - } - - public CurrentState getCurrentState() { return getFirst(); } - public User getSessionUser() { return getSecond(); } - public String getCookieEmail() { return getThird(); } - - } - - public static Optional calculateUserActions( - Optional currentCookie, - WdkModel wdkModel, - SessionProxy session, - String requestPath) { - - UserFactory userFactory = wdkModel.getUserFactory(); - - // three-tuple is: caseNumber, sessionUser, cookieEmail - StateBundle stateBundle = calculateCurrentState(wdkModel, session, currentCookie); - - // only enter synchronized block if action is required - // (i.e. a new user must be added to the session or a logout cookie returned - if (stateBundle.getCurrentState().requiresAction()) { - - // since action cases require creation of a user and assignment on session, synchronize on session - synchronized(session.getUnderlyingSession()) { // must sync on shared object - - // recalculate in case something changed outside the synchronized block - stateBundle = calculateCurrentState(wdkModel, session, currentCookie); - - try { - // determine actions based on state - CookieBuilder cookieToSend = null; - User userToSet = null; - switch (stateBundle.getCurrentState()) { - case REGISTERED_USER_BAD_COOKIE: - cookieToSend = LoginCookieFactory.createLogoutCookie(); - userToSet = createGuest(requestPath, userFactory); - break; - case GUEST_USER_COOKIE_PRESENT: - cookieToSend = LoginCookieFactory.createLogoutCookie(); - // guest user present in session is sufficient - break; - case NO_USER_VALID_COOKIE: - // do not want to update max age on cookie since we have no way to tell GBrowse to also update - userToSet = userFactory.getUserByEmail(stateBundle.getCookieEmail()); - break; - case NO_USER_INVALID_COOKIE: - cookieToSend = LoginCookieFactory.createLogoutCookie(); - userToSet = createGuest(requestPath, userFactory); - break; - case NO_USER_MISSING_COOKIE: - // no cookie necessary - userToSet = createGuest(requestPath, userFactory); - break; - default: - // other cases require no action - break; - } - - // take action as needed - if (userToSet != null) { - session.setAttribute(Utilities.WDK_USER_KEY, userToSet); - Events.triggerAndWait(new NewUserEvent(userToSet, stateBundle.getSessionUser(), session), - new WdkRuntimeException("Unable to complete WDK user assignement.")); - } - return Optional.ofNullable(cookieToSend); - } - catch (Exception ex) { - LOG.error("Caught exception while checking login cookie", ex); - return Optional.of(LoginCookieFactory.createLogoutCookie()); - } - } - } - return Optional.empty(); - } - - private static User createGuest(String requestPath, UserFactory userFactory) { - User guest = userFactory.createUnregistedUser(UnregisteredUserType.GUEST); - LOG.info("Created new guest user [" + guest.getUserId() + "] for request to path: /" + requestPath); - GUEST_CREATION_COUNTER.inc(); - return guest; - } - - private static StateBundle calculateCurrentState(WdkModel wdkModel, SessionProxy session, Optional loginCookie) { - - // get the current user in session and determine type - User wdkUser = (User)session.getAttribute(Utilities.WDK_USER_KEY); - boolean userPresent = (wdkUser != null); - boolean isGuestUser = (userPresent ? wdkUser.isGuest() : false); - - // figure out what's going on with the cookie - boolean cookiePresent = loginCookie.isPresent(); - LoginCookieParts cookieParts = null; - boolean cookieValid = false, cookieMatches = false; - try { - if (cookiePresent) { - LoginCookieFactory auth = new LoginCookieFactory(wdkModel.getModelConfig().getSecretKey()); - cookieParts = LoginCookieFactory.parseCookieValue(loginCookie.get().getValue()); - cookieValid = auth.isValidCookie(cookieParts); - cookieMatches = cookieValid ? (wdkUser != null && cookieParts.getUsername().equals(wdkUser.getEmail())) : false; - } - } - catch (IllegalArgumentException e) { - /* negative values already set */ - } - - CurrentState state = CurrentState.calculateState(userPresent, isGuestUser, cookiePresent, cookieValid, cookieMatches); - - return new StateBundle(state, wdkUser, cookieValid ? cookieParts.getUsername() : null); - } - -} diff --git a/Model/src/main/java/org/gusdb/wdk/model/WdkModel.java b/Model/src/main/java/org/gusdb/wdk/model/WdkModel.java index 444a423e57..bb30e38ac3 100644 --- a/Model/src/main/java/org/gusdb/wdk/model/WdkModel.java +++ b/Model/src/main/java/org/gusdb/wdk/model/WdkModel.java @@ -69,7 +69,6 @@ import org.gusdb.wdk.model.user.BasketFactory; import org.gusdb.wdk.model.user.FavoriteFactory; import org.gusdb.wdk.model.user.StepFactory; -import org.gusdb.wdk.model.user.UnregisteredUser.UnregisteredUserType; import org.gusdb.wdk.model.user.User; import org.gusdb.wdk.model.user.UserFactory; import org.gusdb.wdk.model.user.analysis.StepAnalysisFactory; @@ -1365,7 +1364,7 @@ public User getSystemUser() { try { systemUserLock.lock(); if (systemUser == null) { - systemUser = userFactory.createUnregistedUser(UnregisteredUserType.SYSTEM); + systemUser = userFactory.createUnregistedUser(); } } finally { diff --git a/Model/src/main/java/org/gusdb/wdk/model/config/ModelConfigAccountDB.java b/Model/src/main/java/org/gusdb/wdk/model/config/ModelConfigAccountDB.java index 0307ff8e61..1b1b621b2f 100644 --- a/Model/src/main/java/org/gusdb/wdk/model/config/ModelConfigAccountDB.java +++ b/Model/src/main/java/org/gusdb/wdk/model/config/ModelConfigAccountDB.java @@ -1,15 +1,10 @@ package org.gusdb.wdk.model.config; -import java.util.ArrayList; -import java.util.List; - -import org.gusdb.fgputil.accountdb.UserPropertyName; import org.gusdb.fgputil.db.platform.DBPlatform; public class ModelConfigAccountDB extends ModelConfigDB { private String _accountSchema; - private List _userPropertyNames = new ArrayList(); public void setAccountSchema(String accountSchema) { _accountSchema = DBPlatform.normalizeSchema(accountSchema); @@ -18,12 +13,4 @@ public void setAccountSchema(String accountSchema) { public String getAccountSchema() { return _accountSchema; } - - public void addUserPropertyName(UserPropertyName property) { - _userPropertyNames.add(property); - } - - public List getUserPropertyNames() { - return _userPropertyNames; - } } diff --git a/Model/src/main/java/org/gusdb/wdk/model/config/ModelConfigParser.java b/Model/src/main/java/org/gusdb/wdk/model/config/ModelConfigParser.java index 874f876ca1..ee874200ec 100644 --- a/Model/src/main/java/org/gusdb/wdk/model/config/ModelConfigParser.java +++ b/Model/src/main/java/org/gusdb/wdk/model/config/ModelConfigParser.java @@ -4,7 +4,6 @@ import java.net.URL; import org.apache.commons.digester3.Digester; -import org.gusdb.fgputil.accountdb.UserPropertyName; import org.gusdb.fgputil.xml.XmlParser; import org.gusdb.fgputil.xml.XmlValidator; import org.gusdb.wdk.model.WdkModelException; @@ -65,7 +64,6 @@ private static Digester configureDigester() { // load user db configureNode(digester, "modelConfig/accountDb", ModelConfigAccountDB.class, "setAccountDB"); - configureNode(digester, "modelConfig/accountDb/userProperty", UserPropertyName.class, "addUserPropertyName"); // userdatasetstore configureNode(digester, "modelConfig/userDatasetStore", ModelConfigUserDatasetStore.class, "setUserDatasetStore"); diff --git a/Model/src/main/java/org/gusdb/wdk/model/user/BearerTokenUser.java b/Model/src/main/java/org/gusdb/wdk/model/user/BearerTokenUser.java new file mode 100644 index 0000000000..fb05a0ec48 --- /dev/null +++ b/Model/src/main/java/org/gusdb/wdk/model/user/BearerTokenUser.java @@ -0,0 +1,51 @@ +package org.gusdb.wdk.model.user; + +import org.apache.log4j.Logger; +import org.gusdb.oauth2.client.ValidatedToken; +import org.gusdb.oauth2.shared.token.IdTokenFields; +import org.gusdb.wdk.model.WdkModel; +import org.gusdb.wdk.session.WdkOAuthClientWrapper; +import org.json.JSONObject; + +public class BearerTokenUser extends User { + + private static final Logger LOG = Logger.getLogger(BearerTokenUser.class); + + private final WdkOAuthClientWrapper _client; + private final ValidatedToken _token; + private boolean _userInfoFetched = false; + + public BearerTokenUser(WdkModel wdkModel, WdkOAuthClientWrapper client, ValidatedToken token) { + // parent constructor sets immutable fields provided on the token + super(wdkModel, + Long.valueOf(token.getUserId()), + token.isGuest(), + token.getTokenContents().get(IdTokenFields.signature.name(), String.class), + token.getTokenContents().get(IdTokenFields.preferred_username.name(), String.class)); + _client = client; + _token = token; + } + + @Override + protected void fetchUserInfo() { + // return if already fetched + if (_userInfoFetched) return; + + LOG.info("User data fetch requested for user " + getUserId() + "; querying OAuth server."); + // fetch user info from OAuth server where it is stored (but only on demand, and only once for this object's lifetime) + JSONObject userInfo = _client.getUserData(_token); + + // set email (standard property but mutable so set on user profile and not token + setEmail(userInfo.getString(IdTokenFields.email.name())); + + // set other user properties found only on user profile object + for (WdkUserProperty userProp : User.USER_PROPERTIES.values()) { + userProp.setValue(this, userInfo.optString(userProp.getName(), null)); + } + + LOG.info("User data successfully fetched for " + getDisplayName() + " / " + getOrganization()); + _userInfoFetched = true; + } + +} + diff --git a/Model/src/main/java/org/gusdb/wdk/model/user/RegisteredUser.java b/Model/src/main/java/org/gusdb/wdk/model/user/RegisteredUser.java deleted file mode 100644 index 4c175e00fd..0000000000 --- a/Model/src/main/java/org/gusdb/wdk/model/user/RegisteredUser.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.gusdb.wdk.model.user; - -import org.gusdb.wdk.model.WdkModel; - -public class RegisteredUser extends User { - - public RegisteredUser(WdkModel wdkModel, long userId, String email, String signature, String stableId) { - super(wdkModel, userId, email, signature, stableId); - } - - @Override - public boolean isGuest() { - return false; - } - - @Override - public String getDisplayName() { - return ( - formatNamePart(_properties.get("firstName")) + - formatNamePart(_properties.get("middleName")) + - formatNamePart(_properties.get("lastName"))).trim(); - } - - private static String formatNamePart(String namePart) { - return (namePart == null || namePart.isEmpty() ? "" : " " + namePart.trim()); - } - -} diff --git a/Model/src/main/java/org/gusdb/wdk/model/user/UnregisteredUser.java b/Model/src/main/java/org/gusdb/wdk/model/user/UnregisteredUser.java deleted file mode 100644 index a84b08ad44..0000000000 --- a/Model/src/main/java/org/gusdb/wdk/model/user/UnregisteredUser.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.gusdb.wdk.model.user; - -import org.gusdb.wdk.model.WdkModel; - -public class UnregisteredUser extends User { - - // ------------------------------------------------------------------------- - // types of unregistered users - // ------------------------------------------------------------------------- - - public enum UnregisteredUserType { - GUEST("WDK_GUEST_"), - SYSTEM("WDK_GUEST_SYSTEM_"); - - private final String _stableIdPrefix; - - private UnregisteredUserType(String stableIdPrefix) { - _stableIdPrefix = stableIdPrefix; - } - - public String getStableIdPrefix() { - return _stableIdPrefix; - } - } - - UnregisteredUser(WdkModel wdkModel, long userId, String email, String signature, String stableId) { - super(wdkModel, userId, email, signature, stableId); - } - - @Override - public boolean isGuest() { - return true; - } - - @Override - public String getDisplayName() { - return "WDK Guest"; - } -} diff --git a/Model/src/main/java/org/gusdb/wdk/model/user/User.java b/Model/src/main/java/org/gusdb/wdk/model/user/User.java index dc7a35894c..af44903e2c 100644 --- a/Model/src/main/java/org/gusdb/wdk/model/user/User.java +++ b/Model/src/main/java/org/gusdb/wdk/model/user/User.java @@ -1,58 +1,69 @@ package org.gusdb.wdk.model.user; -import java.util.HashMap; +import java.util.Collections; +import java.util.List; import java.util.Map; +import org.gusdb.fgputil.accountdb.UserPropertyName; +import org.gusdb.fgputil.functional.Functions; import org.gusdb.wdk.model.WdkModel; /** * Represents a WDK user. - * - * @see UnregisteredUser for subclass representing guest user - * @see RegisteredUser for subclass representing registered user - * + * * @author rdoherty */ -public abstract class User { - - protected WdkModel _wdkModel; - - protected long _userId; - protected String _email; - protected String _signature; - protected String _stableId; +public class User { + + public static final Map USER_PROPERTIES = createUserPropertyDefs(); + + private static Map createUserPropertyDefs() { + List userProps = List.of( + new WdkUserProperty("username", "Username", "username", false, false, false, User::getUsername, User::setUsername), + new WdkUserProperty("firstName", "First Name", "first_name", true, true, false, User::getFirstName, User::setFirstName), + new WdkUserProperty("middleName", "Middle Name", "middle_name", false, true, false, User::getMiddleName, User::setMiddleName), + new WdkUserProperty("lastName", "Last Name", "last_name", true, true, false, User::getLastName, User::setLastName), + new WdkUserProperty("organization", "Organization", "organization", true, true, false, User::getOrganization, User::setOrganization), + new WdkUserProperty("interests", "Interests", "interests", false, false, true, User::getInterests, User::setInterests) + ); + return Collections.unmodifiableMap(Functions.getMapFromValues(userProps, UserPropertyName::getName)); + } - // Holds key/value pairs associated with the user's profile (come from account db) - protected Map _properties = new HashMap<>(); + private final WdkModel _wdkModel; - /** - * Provides a "pretty" display name for this user - * - * @return display name for this user - */ - public abstract String getDisplayName(); + // immutable fields supplied by bearer token + private final long _userId; + private final boolean _isGuest; + private final String _signature; + private final String _stableId; - /** - * Tells whether this user is a guest - * - * @return true if guest, else false - */ - public abstract boolean isGuest(); + // mutable fields that may need to be fetched + private String _email; // standard; not a user property + private String _username; + private String _firstName; + private String _middleName; + private String _lastName; + private String _organization; + private String _interests; - protected User(WdkModel wdkModel, long userId, String email, String signature, String stableId) { + public User(WdkModel wdkModel, long userId, boolean isGuest, String signature, String stableId) { _wdkModel = wdkModel; _userId = userId; - _email = email; + _isGuest = isGuest; _signature = signature; _stableId = stableId; } + public WdkModel getWdkModel() { + return _wdkModel; + } + public long getUserId() { return _userId; } - public String getEmail() { - return _email; + public boolean isGuest() { + return _isGuest; } public String getSignature() { @@ -63,33 +74,94 @@ public String getStableId() { return _stableId; } - public void setEmail(String email) { + protected void fetchUserInfo() { + // nothing to do in this base class; all info must be explicitly set + } + + public String getEmail() { + fetchUserInfo(); + return _email; + } + + public User setEmail(String email) { _email = email; + return this; } - /** - * Sets the value of the profile property given by the UserProfileProperty enum - * @param key - * @param value - */ - public void setProfileProperty(String key, String value) { - _properties.put(key, value); + public String getUsername() { + fetchUserInfo(); + return _username; } - public void setProfileProperties(Map properties) { - _properties = properties; + public User setUsername(String username) { + _username = username; + return this; + } + + public String getFirstName() { + fetchUserInfo(); + return _firstName; + } + + public User setFirstName(String firstName) { + _firstName = firstName; + return this; + } + + public String getMiddleName() { + fetchUserInfo(); + return _middleName; + } + + public User setMiddleName(String middleName) { + _middleName = middleName; + return this; + } + + public String getLastName() { + fetchUserInfo(); + return _lastName; + } + + public User setLastName(String lastName) { + _lastName = lastName; + return this; + } + + public String getOrganization() { + fetchUserInfo(); + return _organization; + } + + public User setOrganization(String organization) { + _organization = organization; + return this; + } + + public String getInterests() { + fetchUserInfo(); + return _interests; + } + + public User setInterests(String interests) { + _interests = interests; + return this; } /** - * Return the entire user profile property map - * @return + * Provides a "pretty" display name for this user + * + * @return display name for this user */ - public Map getProfileProperties() { - return _properties; + public String getDisplayName() { + return isGuest() ? "WDK Guest" : ( + formatNamePart(getFirstName()) + + formatNamePart(getMiddleName()) + + formatNamePart(getLastName())).trim(); } - public WdkModel getWdkModel() { - return _wdkModel; + private static String formatNamePart(String namePart) { + return (namePart == null || namePart.isEmpty() ? "" : " " + namePart.trim()); } @Override @@ -99,7 +171,7 @@ public String toString() { @Override public int hashCode() { - return (int)getUserId(); + return String.valueOf(getUserId()).hashCode(); } @Override @@ -107,13 +179,7 @@ public boolean equals(Object obj) { if (!(obj instanceof User)) { return false; } - User other = (User)obj; - return ( - getUserId() == other.getUserId() && - getEmail().equals(other.getEmail()) && - getSignature().equals(other.getSignature()) && - getStableId().equals(other.getStableId()) - ); + return getUserId() == ((User)obj).getUserId(); } } diff --git a/Model/src/main/java/org/gusdb/wdk/model/user/UserCache.java b/Model/src/main/java/org/gusdb/wdk/model/user/UserCache.java index 326ff7687a..519739b8eb 100644 --- a/Model/src/main/java/org/gusdb/wdk/model/user/UserCache.java +++ b/Model/src/main/java/org/gusdb/wdk/model/user/UserCache.java @@ -28,17 +28,20 @@ public UserCache(User user) { @Override public User get(Object id) { try { - Long userId = (Long)id; - if (userId == null) { + if (id == null) { throw new WdkRuntimeException("User ID cannot be null."); } + if (!(id instanceof Long)) { + throw new IllegalArgumentException("Only Long objects should be passed to this method, not " + id.getClass().getName()); + } + Long userId = (Long)id; if (!containsKey(userId)) { if (_userFactory != null) { put(userId, _userFactory.getUserById(userId) - .orElseThrow(() -> new WdkRuntimeException("User with ID " + id + " does not exist."))); + .orElseThrow(() -> new WdkRuntimeException("User with ID " + userId + " does not exist."))); } else { - throw new WdkRuntimeException("No-lookup cache does not contain the requested user (" + id + ")."); + throw new WdkRuntimeException("No-lookup cache does not contain the requested user (" + userId + ")."); } } return super.get(userId); diff --git a/Model/src/main/java/org/gusdb/wdk/model/user/UserCreationScript.java b/Model/src/main/java/org/gusdb/wdk/model/user/UserCreationScript.java deleted file mode 100644 index 1e9750fc97..0000000000 --- a/Model/src/main/java/org/gusdb/wdk/model/user/UserCreationScript.java +++ /dev/null @@ -1,165 +0,0 @@ -package org.gusdb.wdk.model.user; - -import static org.gusdb.fgputil.FormatUtil.NL; -import static org.gusdb.fgputil.FormatUtil.TAB; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.gusdb.fgputil.FormatUtil; -import org.gusdb.fgputil.accountdb.UserPropertyName; -import org.gusdb.fgputil.runtime.GusHome; -import org.gusdb.wdk.model.WdkModel; -import org.gusdb.wdk.model.WdkModelException; - -public class UserCreationScript { - - public static class UserLine { - - private final boolean _shouldWriteUser; - private final String _email; - private final Map _globalUserPrefs; - private final Map _userProperties; - - public UserLine(boolean shouldWriteUser, String email, - Map globalUserPrefs, Map userProperties) { - _shouldWriteUser = shouldWriteUser; - _email = email; - _globalUserPrefs = globalUserPrefs; - _userProperties = userProperties; - } - - public boolean shouldWriteUser() { return _shouldWriteUser; } - public String getEmail() { return _email; } - public Map getGlobalUserPrefs() { return _globalUserPrefs; } - public Map getUserProperties() { return _userProperties; } - - public String getAttributesString() { return getEmail() + ", " + - FormatUtil.prettyPrint(_userProperties) + ", " + - FormatUtil.prettyPrint(_globalUserPrefs); } - - @Override - public String toString() { - return shouldWriteUser() + ", " + getAttributesString(); - } - } - - public static void main(String[] args) throws WdkModelException, IOException { - if (!(args.length == 1 || (args.length == 2 && args[1].equalsIgnoreCase("test")))) { - System.err.println(NL + - "USAGE: fgpJava " + UserCreationScript.class.getName() + " [test]" + NL + NL + - "This script will read tab-delimited user properties from stdin" + NL + - "Passed project_id value is used only to look up account-db access information in gus_home" + NL + - "If 'test' is specified as a second argument, no records will be written to the DB; " + - "instead diagnostics will be printed to stdout" + NL); - System.exit(1); - } - boolean testOnly = args.length == 2; - try (WdkModel model = WdkModel.construct(args[0], GusHome.getGusHome()); - BufferedReader in = new BufferedReader(new InputStreamReader(System.in))) { - List userProps = model.getModelConfig().getAccountDB().getUserPropertyNames(); - int newUserCount = 0, modifiedUserCount = 0, invalidLineCount = 0; - String line; - while ((line = in.readLine()) != null) { - UserLine parsedLine = parseLine(line, userProps); - if (parsedLine.shouldWriteUser()) { - try { - // create or edit user - User user = model.getUserFactory().getUserByEmail(parsedLine.getEmail()); - if (user == null) { - newUserCount++; - if (testOnly) { - System.out.println("Would create user: " + parsedLine.getAttributesString()); - } - else { - // create new user and assign preferences - user = model.getUserFactory().createUser( - parsedLine.getEmail(), parsedLine.getUserProperties(), true, false); - new UserPreferenceFactory(model).savePreferences(user.getUserId(), - new UserPreferences(parsedLine.getGlobalUserPrefs(), Collections.emptyMap())); - - - System.out.println("Created user with ID " + user.getUserId() + " and email " + user.getEmail()); - } - } - else { - modifiedUserCount++; - String message = "User with email " + user.getEmail() + - " exists; %s preferences " + FormatUtil.prettyPrint(parsedLine.getGlobalUserPrefs()); - if (testOnly) { - System.out.println(String.format(message, "would add")); - } - else { - // user exists already; simply add preferences - UserPreferenceFactory prefsFactory = new UserPreferenceFactory(model); - UserPreferences userPrefs = prefsFactory.getPreferences(user.getUserId()); - for (Entry newPref : parsedLine.getGlobalUserPrefs().entrySet()) { - userPrefs.setGlobalPreference(newPref.getKey(), newPref.getValue()); - } - prefsFactory.savePreferences(user.getUserId(), userPrefs); - System.out.println(String.format(message, "adding")); - } - } - } - catch (InvalidUsernameOrEmailException e) { - System.err.println("Invalid email '" + parsedLine.getEmail() + "': " + e.getMessage()); - invalidLineCount++; - } - } - else { - invalidLineCount++; - } - } - System.out.println("Number of new users: " + newUserCount); - System.out.println("Number of existing users we added preferences to (could be more than one project): " + modifiedUserCount); - System.out.println("Number of lines with invalid input: " + invalidLineCount); - } - } - - static UserLine parseLine( - String line, List userProps) { - String[] tokens = line.split(TAB); - if (tokens.length == 0 || tokens[0].trim().isEmpty()) { - System.err.println("Required value [email] missing on line: " + line); - return new UserLine(false, null, null, null); - } - String email = tokens[0]; - - // next token is project_ids user wants emails from - Map globalUserPrefs = (tokens.length > 1 ? - getEmailPrefsFromProjectIds(tokens[1]) : Collections.emptyMap()); - - boolean valid = true; - Map propertyMap = new LinkedHashMap<>(); - for (int i = 0; i < userProps.size(); i++) { - UserPropertyName propName = userProps.get(i); - // split will trim off trailing empty tokens, so backfill - String nextValue = tokens.length > i + 2 ? tokens[i + 2].trim() : ""; - if (propName.isRequired() && nextValue.isEmpty()) { - System.err.println("Required value [" + propName.getName() + "] missing on line: " + line); - valid = false; - } - propertyMap.put(userProps.get(i).getName(), nextValue); - } - return new UserLine(valid, email, globalUserPrefs, propertyMap); - } - - private static Map getEmailPrefsFromProjectIds(String commaDelimitedListOfProjectIds) { - String[] projectIds = commaDelimitedListOfProjectIds.trim().isEmpty() ? - new String[0] : commaDelimitedListOfProjectIds.split(","); - return Arrays.stream(projectIds) - .filter(projectId -> !projectId.trim().isEmpty()) - .map(projectId -> "preference_global_email_" + projectId.trim().toLowerCase()) - .collect(Collectors.toMap(Function.identity(), val -> "on")); - - } -} diff --git a/Model/src/main/java/org/gusdb/wdk/model/user/UserFactory.java b/Model/src/main/java/org/gusdb/wdk/model/user/UserFactory.java index 11b6518db2..5e1ce04f26 100644 --- a/Model/src/main/java/org/gusdb/wdk/model/user/UserFactory.java +++ b/Model/src/main/java/org/gusdb/wdk/model/user/UserFactory.java @@ -9,6 +9,7 @@ import java.util.regex.Matcher; import org.apache.log4j.Logger; +import org.gusdb.fgputil.MapBuilder; import org.gusdb.fgputil.accountdb.AccountManager; import org.gusdb.fgputil.accountdb.UserProfile; import org.gusdb.fgputil.db.pool.DatabaseInstance; @@ -24,7 +25,6 @@ import org.gusdb.wdk.model.WdkUserException; import org.gusdb.wdk.model.config.ModelConfig; import org.gusdb.wdk.model.config.ModelConfigAccountDB; -import org.gusdb.wdk.model.user.UnregisteredUser.UnregisteredUserType; /** * Manages persistence of user profile and preferences and creation and @@ -87,20 +87,18 @@ public class UserFactory { private final WdkModel _wdkModel; private final DatabaseInstance _userDb; private final String _userSchema; - private final AccountManager _accountManager; // ------------------------------------------------------------------------- // constructor // ------------------------------------------------------------------------- public UserFactory(WdkModel wdkModel) { + // save model for populating new users _wdkModel = wdkModel; _userDb = wdkModel.getUserDb(); ModelConfig modelConfig = wdkModel.getModelConfig(); _userSchema = modelConfig.getUserDB().getUserSchema(); ModelConfigAccountDB accountDbConfig = modelConfig.getAccountDB(); - _accountManager = new AccountManager(wdkModel.getAccountDb(), - accountDbConfig.getAccountSchema(), accountDbConfig.getUserPropertyNames()); } // ------------------------------------------------------------------------- @@ -144,9 +142,7 @@ public User createUser(String email, } // create new user object and add profile properties - RegisteredUser user = new RegisteredUser(_wdkModel, profile.getUserId(), - profile.getEmail(), profile.getSignature(), profile.getStableId()); - user.setProfileProperties(profile.getProperties()); + User user = getUserFromProfile(profile); // if needed, send user temporary password via email if (sendWelcomeEmail) { @@ -165,6 +161,18 @@ public User createUser(String email, } } + private User getUserFromProfile(UserProfile profile) { + if (profile == null) return null; + Map properties = profile.getProperties(); + return new User(_wdkModel, profile.getUserId(), + false, profile.getSignature(), profile.getStableId()) + .setEmail(profile.getEmail()) + .setFirstName(properties.get("firstName")) + .setMiddleName(properties.get("middleName")) + .setLastName(properties.get("lastName")) + .setOrganization(properties.get("organization")); + } + private void addUserReference(Long userId, boolean isGuest) { Timestamp insertedOn = new Timestamp(new Date().getTime()); String sql = INSERT_USER_REF_SQL @@ -259,11 +267,11 @@ private static String generateTemporaryPassword() { * @return new unregistered user with remaining fields populated * @throws WdkRuntimeException if unable to persist temporary user */ - public UnregisteredUser createUnregistedUser(UnregisteredUserType userType) throws WdkRuntimeException { + public User createUnregistedUser() throws WdkRuntimeException { try { - UserProfile profile = _accountManager.createGuestAccount(userType.getStableIdPrefix()); + UserProfile profile = _accountManager.createGuestAccount("GUEST_"); addUserReference(profile.getUserId(), true); - return new UnregisteredUser(_wdkModel, profile.getUserId(), profile.getEmail(), profile.getSignature(), profile.getStableId()); + return new User(_wdkModel, profile.getUserId(), true, profile.getSignature(), profile.getStableId()).setEmail(profile.getEmail()); } catch (Exception e) { throw new WdkRuntimeException("Unable to save temporary user", e); @@ -317,7 +325,7 @@ public boolean isCorrectPassword(String usernameOrEmail, String password) throws } private User authenticate(String usernameOrEmail, String password) { - return populateRegisteredUser(_accountManager.getUserProfile(usernameOrEmail, password)); + return getUserFromProfile(_accountManager.getUserProfile(usernameOrEmail, password)); } /** @@ -331,16 +339,16 @@ public Optional getUserById(long userId) throws WdkModelException { UserProfile profile = _accountManager.getUserProfile(userId); if (profile != null) { // found registered user in account DB; create RegisteredUser and populate - return Optional.of(populateRegisteredUser(profile)); + return Optional.of(getUserFromProfile(profile)); } else { // cannot find user in account DB; however, the passed ID may represent a guest local to this userDb Date accessDate = getGuestUserRefFirstAccess(userId); if (accessDate != null) { // guest user was found in local user Db; create UnregisteredUser and populate - profile = AccountManager.createGuestProfile(UnregisteredUserType.GUEST.getStableIdPrefix(), userId, accessDate); - return Optional.of(new UnregisteredUser(_wdkModel, profile.getUserId(), - profile.getEmail(), profile.getSignature(), profile.getStableId())); + profile = AccountManager.createGuestProfile("GUEST_", userId, accessDate); + return Optional.of(new User(_wdkModel, profile.getUserId(), true, + profile.getSignature(), profile.getStableId()).setEmail(profile.getEmail())); } else { @@ -351,28 +359,11 @@ public Optional getUserById(long userId) throws WdkModelException { } public User getUserByEmail(String email) { - return populateRegisteredUser(_accountManager.getUserProfileByEmail(email)); + return getUserFromProfile(_accountManager.getUserProfileByEmail(email)); } private User getUserProfileByUsernameOrEmail(String usernameOrEmail) { - return populateRegisteredUser(_accountManager.getUserProfileByUsernameOrEmail(usernameOrEmail)); - } - - public User getUserBySignature(String signature) throws WdkUserException { - User user = populateRegisteredUser(_accountManager.getUserProfileBySignature(signature)); - if (user == null) { - // signature is rarely sent in by user; if User cannot be found, it's probably an error - throw new WdkUserException("Unable to find user with signature: " + signature); - } - return user; - } - - private User populateRegisteredUser(UserProfile profile) { - if (profile == null) return null; - User user = new RegisteredUser(_wdkModel, profile.getUserId(), profile.getEmail(), - profile.getSignature(), profile.getStableId()); - user.setProfileProperties(profile.getProperties()); - return user; + return getUserFromProfile(_accountManager.getUserProfileByUsernameOrEmail(usernameOrEmail)); } /** @@ -408,7 +399,13 @@ public void saveUser(User user) throws WdkModelException, InvalidUsernameOrEmail } // save off other data to user profile - _accountManager.saveUserProfile(user.getUserId(), user.getEmail(), user.getProfileProperties()); + _accountManager.saveUserProfile(user.getUserId(), user.getEmail(), new MapBuilder() + .put("firstName", user.getFirstName()) + .put("middleName", user.getMiddleName()) + .put("lastName", user.getLastName()) + .put("organization", user.getOrganization()) + .toMap() + ); // get updated profile and trigger profile update event UserProfile newProfile = _accountManager.getUserProfile(user.getUserId()); @@ -440,4 +437,12 @@ public void resetPassword(String emailOrLoginName) throws WdkUserException, WdkM public void changePassword(long userId, String newPassword) { _accountManager.updatePassword(userId, newPassword); } + + // FIXME: should be atomic + public void insertUserToUserDb(User user) { + // make sure user has reference in this user DB (needs to happen before merging) + if (!hasUserReference(user.getUserId())) { + addUserReference(user.getUserId(), user.isGuest()); + } + } } diff --git a/Model/src/main/java/org/gusdb/wdk/model/user/WdkUserProperty.java b/Model/src/main/java/org/gusdb/wdk/model/user/WdkUserProperty.java new file mode 100644 index 0000000000..276ed80c03 --- /dev/null +++ b/Model/src/main/java/org/gusdb/wdk/model/user/WdkUserProperty.java @@ -0,0 +1,32 @@ +package org.gusdb.wdk.model.user; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +import org.gusdb.fgputil.accountdb.UserPropertyName; + +/** + * Convenience class to work around accountdb API and interface with WDK User class + */ +public class WdkUserProperty extends UserPropertyName { + + private final Function _getter; + private final BiConsumer _setter; + + public WdkUserProperty(String name, String displayName, String dbKey, boolean isRequired, boolean isPublic, boolean isMultiLine, Function getter, BiConsumer setter) { + UserPropertyName userProp = new UserPropertyName(name, dbKey, isRequired); + userProp.setDisplayName(displayName); + userProp.setPublic(isPublic); + userProp.setMultiLine(isMultiLine); + _getter = getter; + _setter = setter; + } + + public String getValue(User user) { + return _getter.apply(user); + } + + public void setValue(User user, String value) { + _setter.accept(user, value); + } +} diff --git a/Model/src/main/java/org/gusdb/wdk/session/WdkOAuthClientWrapper.java b/Model/src/main/java/org/gusdb/wdk/session/WdkOAuthClientWrapper.java index 9158f8193f..52ea09d7bb 100644 --- a/Model/src/main/java/org/gusdb/wdk/session/WdkOAuthClientWrapper.java +++ b/Model/src/main/java/org/gusdb/wdk/session/WdkOAuthClientWrapper.java @@ -1,44 +1,24 @@ package org.gusdb.wdk.session; -import org.apache.log4j.Logger; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + import org.gusdb.oauth2.client.OAuthClient; -import org.gusdb.oauth2.client.OAuthClient.ValidatedToken; import org.gusdb.oauth2.client.OAuthConfig; -import org.gusdb.oauth2.shared.token.IdTokenFields; +import org.gusdb.oauth2.client.ValidatedToken; import org.gusdb.wdk.model.WdkModel; -import org.gusdb.wdk.model.WdkModelException; -import org.gusdb.wdk.model.user.RegisteredUser; -import org.gusdb.wdk.model.user.User; -import org.gusdb.wdk.model.user.UserFactory; +import org.json.JSONArray; import org.json.JSONObject; -import io.jsonwebtoken.Claims; - public class WdkOAuthClientWrapper { - private static final Logger LOG = Logger.getLogger(WdkOAuthClientWrapper.class); - - private final WdkModel _wdkModel; private final OAuthConfig _config; private final OAuthClient _client; - public WdkOAuthClientWrapper(WdkModel wdkModel) throws WdkModelException { - try { - _wdkModel = wdkModel; - _config = wdkModel.getModelConfig(); - _client = new OAuthClient(OAuthClient.getTrustManager(wdkModel.getModelConfig())); - } - catch (Exception e) { - throw new WdkModelException("Unable to instantiate OAuthClient from config", e); - } - } - - public ValidatedToken getIdTokenFromAuthCode(String authCode, String redirectUri) { - return _client.getIdTokenFromAuthCode(_config, authCode, redirectUri); - } - - public ValidatedToken getIdTokenFromCredentials(String email, String password, String redirectUrl) { - return _client.getIdTokenFromUsernamePassword(_config, email, password, redirectUrl); + public WdkOAuthClientWrapper(WdkModel wdkModel) { + _config = wdkModel.getModelConfig(); + _client = new OAuthClient(OAuthClient.getTrustManager(wdkModel.getModelConfig())); } public ValidatedToken getBearerTokenFromAuthCode(String authCode, String redirectUri) { @@ -49,46 +29,25 @@ public ValidatedToken getBearerTokenFromCredentials(String email, String passwor return _client.getBearerTokenFromUsernamePassword(_config, email, password, redirectUrl); } - public User getUserFromValidatedToken(ValidatedToken token, UserFactory userFactory) { - Claims claims = token.getTokenContents(); - - User user = new RegisteredUser(_wdkModel, - Long.parseLong(claims.getSubject()), - claims.get(IdTokenFields.email.name(), String.class), - claims.get(IdTokenFields.signature.name(), String.class), - claims.get(IdTokenFields.preferred_username.name(), String.class)); - - // have to hit the user data endpoint for this for user details; bearer token does not include all fields - JSONObject userData = _client.getUserData(_config, token); + public ValidatedToken validateBearerToken(String rawToken) { + return _client.getValidatedEcdsaSignedToken(_config.getOauthUrl(), rawToken); + } - // user UserFactory to create user from the JSON object, adding to DB if necessary for FKs on other userlogins5 tables - User user = new UserFactory(wdkModel). - //long userId = client.getUserIdFromAuthCode(authCode, appUrl); - //User newUser = wdkModel.getUserFactory().login(userId); - if (newUser == null) { - throw new WdkModelException("Unable to find user with ID " + userId + - ", returned by OAuth service with auth code " + authCode); - } + public JSONObject getUserData(ValidatedToken token) { + return _client.getUserData(_config.getOauthUrl(), token); } - /* Leaving here temporarily as a reference - private static long getUserIdFromIdToken(String idToken, String clientSecret) throws WdkModelException { - try { - LOG.debug("Attempting parse of id token [" + idToken + "] using client secret '" + clientSecret +"'"); - String encodedKey = TextCodec.BASE64.encode(clientSecret); - Claims claims = Jwts.parser().setSigningKey(encodedKey).parseClaimsJws(idToken).getBody(); - // TODO: verify additional claims for security - String userIdStr = claims.getSubject(); - LOG.debug("Received token for sub '" + userIdStr + "' and preferred_username '" + claims.get("preferred_username")); - if (FormatUtil.isInteger(userIdStr)) { - return Long.valueOf(userIdStr); - } - throw new WdkModelException("Subject returned by OAuth server [" + userIdStr + "] is not a valid user ID."); - } - catch (JSONException e) { - throw new WdkModelException("JWT body returned is not a valid JSON object."); + public List getUserData(List userIds) { + List stringIds = userIds.stream().map(String::valueOf).collect(Collectors.toList()); + JSONArray usersJson = _client.getUserData(_config, stringIds); + List users = new ArrayList<>(); + for (int i = 0; i < usersJson.length(); i++) { + users.add(usersJson.getJSONObject(i)); } + return users; + } + public ValidatedToken getNewGuestToken() { + return _client.getNewGuestToken(_config); } - */ } diff --git a/Model/src/test/java/org/gusdb/wdk/model/user/UserCreationScriptTest.java b/Model/src/test/java/org/gusdb/wdk/model/user/UserCreationScriptTest.java deleted file mode 100644 index 3fece5e659..0000000000 --- a/Model/src/test/java/org/gusdb/wdk/model/user/UserCreationScriptTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.gusdb.wdk.model.user; - -import java.util.Arrays; -import java.util.List; - -import org.gusdb.fgputil.accountdb.UserPropertyName; -import org.gusdb.wdk.model.user.UserCreationScript.UserLine; -import org.junit.Test; - -public class UserCreationScriptTest { - - private static final String[] TEST_CASES = { - "\ta\tb\tc\td", - "email\ta\t\tc\td", - "email\t\tb\tc\td", - "email\ta\tb\t\t\t\t" - }; - - private static final UserPropertyName[] USER_PROPS = { - new UserPropertyName("firstName", null, true), - new UserPropertyName("middleName", null, false), - new UserPropertyName("lastName", null, true), - new UserPropertyName("organization", null, true) - }; - - @Test - public void testParsing() { - List userProps = Arrays.asList(USER_PROPS); - for (String testCase : TEST_CASES) { - UserLine userLine = UserCreationScript.parseLine(testCase, userProps); - System.out.println(userLine); - } - } -} diff --git a/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java b/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java index d4ea31b860..0358b6db1b 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java +++ b/Service/src/main/java/org/gusdb/wdk/service/filter/CheckLoginFilter.java @@ -1,8 +1,6 @@ package org.gusdb.wdk.service.filter; import java.io.IOException; -import java.util.Map; -import java.util.Optional; import javax.annotation.Priority; import javax.inject.Inject; @@ -16,24 +14,42 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.Cookie; import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedMap; +import org.apache.log4j.Logger; import org.glassfish.grizzly.http.server.Request; import org.gusdb.fgputil.web.ApplicationContext; import org.gusdb.fgputil.web.CookieBuilder; -import org.gusdb.fgputil.web.LoginCookieFactory; import org.gusdb.fgputil.web.RequestData; +import org.gusdb.oauth2.client.OAuthClient; +import org.gusdb.oauth2.client.ValidatedToken; import org.gusdb.wdk.controller.ContextLookup; -import org.gusdb.wdk.controller.filter.CheckLoginFilterShared; +import org.gusdb.wdk.model.Utilities; +import org.gusdb.wdk.model.WdkModel; +import org.gusdb.wdk.model.user.BearerTokenUser; +import org.gusdb.wdk.model.user.User; +import org.gusdb.wdk.service.service.SessionService; import org.gusdb.wdk.service.service.SystemService; +import org.gusdb.wdk.session.WdkOAuthClientWrapper; + +import io.prometheus.client.Counter; @Priority(30) public class CheckLoginFilter implements ContainerRequestFilter, ContainerResponseFilter { - public static final String SESSION_COOKIE_TO_SET = "sessionCookieToSet"; + private static final Logger LOG = Logger.getLogger(CheckLoginFilter.class); + + private static final Counter GUEST_CREATION_COUNTER = Counter.build() + .name("wdk_guest_creation_count") + .help("Number of guest users created by WDK services") + .register(); + + private static final String TOKEN_COOKIE_VALUE_TO_SET = "tokenCookieValueToSet"; + + private static final String LEGACY_WDK_LOGIN_COOKIE_NAME = "wdk_check_auth"; @Context - protected - ServletContext _servletContext; + protected ServletContext _servletContext; @Inject protected Provider _servletRequest; @@ -43,23 +59,62 @@ public class CheckLoginFilter implements ContainerRequestFilter, ContainerRespon @Override public void filter(ContainerRequestContext requestContext) throws IOException { - - // skip certain endpoints to avoid a guest user being created for those endpoints + // skip endpoints which do not require a user; prevents guests from being unnecessarily created String requestPath = requestContext.getUriInfo().getPath(); if (isPathToSkip(requestPath)) return; ApplicationContext context = ContextLookup.getApplicationContext(_servletContext); RequestData request = ContextLookup.getRequest(_servletRequest.get(), _grizzlyRequest.get()); + WdkModel wdkModel = ContextLookup.getWdkModel(context); + WdkOAuthClientWrapper oauth = new WdkOAuthClientWrapper(wdkModel); + + // try to find submitted bearer token + String rawToken = findRawBearerToken(request, requestContext); + + try { + ValidatedToken token; + User user; + if (rawToken != null) { + // validate submitted token + token = oauth.validateBearerToken(rawToken); + user = new BearerTokenUser(wdkModel, oauth, token); + + LOG.info("Validated successfully. Request will be processed for user " + user.getUserId() + " / " + user.getEmail()); + } + else { + // no credentials submitted; automatically create a guest to use on this request + token = oauth.getNewGuestToken(); + user = new BearerTokenUser(wdkModel, oauth, token); + + LOG.info("Created new guest user [" + user.getUserId() + "] for request to path: /" + requestPath); + GUEST_CREATION_COUNTER.inc(); + + // set flag indicating that cookies should be added to response containing the new token + requestContext.setProperty(TOKEN_COOKIE_VALUE_TO_SET, token.getTokenValue()); + } + + // insert reference to this user into user DB for tracking and for foreign keys on other user DB tables + wdkModel.getUserFactory().insertUserToUserDb(user); + + // set user on the request object for use by this request's processing + request.setAttribute(Utilities.WDK_USER_KEY, user); + } + catch (Exception e) { + // for now, log and let this go, deferring to legacy authentication + LOG.error("Unable to authenticate with Authorization header " + rawToken, e); + throw e; + } + } - Optional newCookie = - CheckLoginFilterShared.calculateUserActions( - findLoginCookie(requestContext.getCookies()), - ContextLookup.getWdkModel(context), - request.getSession(), requestPath); - - if (newCookie.isPresent()) { - requestContext.setProperty(SESSION_COOKIE_TO_SET, newCookie.get().toJaxRsCookie().toString()); + private String findRawBearerToken(RequestData request, ContainerRequestContext requestContext) { + String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION); + if (authHeader != null) { + LOG.info("Recieved Authorization header with value: " + authHeader + "; trying bearer token validation."); + return OAuthClient.getTokenFromAuthHeader(authHeader); } + // otherwise try Authorization cookie + Cookie cookie = requestContext.getCookies().get(HttpHeaders.AUTHORIZATION); + return cookie == null ? null : cookie.getValue(); } protected boolean isPathToSkip(String path) { @@ -67,16 +122,24 @@ protected boolean isPathToSkip(String path) { return SystemService.PROMETHEUS_ENDPOINT_PATH.equals(path); } - protected Optional findLoginCookie(Map cookies) { - return Optional.ofNullable(cookies.get(LoginCookieFactory.WDK_LOGIN_COOKIE_NAME)) - .map(cookie -> new CookieBuilder(cookie.getName(), cookie.getValue())); - } - @Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { - if (requestContext.getPropertyNames().contains(SESSION_COOKIE_TO_SET)) { - responseContext.getHeaders().add(HttpHeaders.SET_COOKIE, requestContext.getProperty(SESSION_COOKIE_TO_SET)); + MultivaluedMap headers = responseContext.getHeaders(); + if (requestContext.getPropertyNames().contains(TOKEN_COOKIE_VALUE_TO_SET)) { + String tokenValue = (String)requestContext.getProperty(TOKEN_COOKIE_VALUE_TO_SET); + + // set cookie value for both Authorization cookie + CookieBuilder cookie = new CookieBuilder(HttpHeaders.AUTHORIZATION, tokenValue); + cookie.setMaxAge(SessionService.EXPIRATION_3_YEARS_SECS); + cookie.setPath("/"); + headers.add(HttpHeaders.SET_COOKIE, cookie.toJaxRsCookie().toString()); } + + // unset legacy WDK check auth cookie in case it is present + CookieBuilder cookie = new CookieBuilder(LEGACY_WDK_LOGIN_COOKIE_NAME, ""); + cookie.setMaxAge(0); + cookie.setPath("/"); + headers.add(HttpHeaders.SET_COOKIE, cookie.toJaxRsCookie().toString()); } } diff --git a/Service/src/main/java/org/gusdb/wdk/service/formatter/ProjectFormatter.java b/Service/src/main/java/org/gusdb/wdk/service/formatter/ProjectFormatter.java index 466fcae614..ac12e63a1e 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/formatter/ProjectFormatter.java +++ b/Service/src/main/java/org/gusdb/wdk/service/formatter/ProjectFormatter.java @@ -1,9 +1,12 @@ package org.gusdb.wdk.service.formatter; +import java.util.Optional; + import org.gusdb.fgputil.accountdb.UserPropertyName; import org.gusdb.wdk.core.api.JsonKeys; import org.gusdb.wdk.model.WdkModel; import org.gusdb.wdk.model.config.ModelConfig; +import org.gusdb.wdk.model.user.User; import org.json.JSONArray; import org.json.JSONObject; @@ -47,7 +50,7 @@ public static JSONObject getWdkProjectInfo(WdkModel wdkModel, String serviceEndp // create profile property config sub-array JSONArray userProfileProps = new JSONArray(); - for (UserPropertyName prop : wdkModel.getModelConfig().getAccountDB().getUserPropertyNames()) { + for (UserPropertyName prop : User.USER_PROPERTIES.values()) { userProfileProps.put(new JSONObject() .put(JsonKeys.NAME, prop.getName()) .put(JsonKeys.DISPLAY_NAME, prop.getDisplayName()) @@ -57,8 +60,7 @@ public static JSONObject getWdkProjectInfo(WdkModel wdkModel, String serviceEndp } return new JSONObject() - .put(JsonKeys.DESCRIPTION, wdkModel.getIntroduction() == null ? - WELCOME_MESSAGE : wdkModel.getIntroduction()) + .put(JsonKeys.DESCRIPTION, Optional.ofNullable(wdkModel.getIntroduction()).orElse(WELCOME_MESSAGE)) .put(JsonKeys.DISPLAY_NAME, wdkModel.getDisplayName()) .put(JsonKeys.PROJECT_ID, wdkModel.getProjectId()) .put(JsonKeys.BUILD_NUMBER, wdkModel.getBuildNumber()) diff --git a/Service/src/main/java/org/gusdb/wdk/service/formatter/StrategyFormatter.java b/Service/src/main/java/org/gusdb/wdk/service/formatter/StrategyFormatter.java index 4eac4458b1..9d27a70947 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/formatter/StrategyFormatter.java +++ b/Service/src/main/java/org/gusdb/wdk/service/formatter/StrategyFormatter.java @@ -45,7 +45,7 @@ private static JSONObject getListingStrategyJson(Strategy strategy, boolean incl .put(JsonKeys.IS_VALID, strategy.isValid()) .put(JsonKeys.IS_DELETED, strategy.isDeleted()) .put(JsonKeys.IS_EXAMPLE, strategy.isExample()) - .put(JsonKeys.ORGANIZATION, strategy.getUser().getProfileProperties().get("organization")) + .put(JsonKeys.ORGANIZATION, strategy.getUser().getOrganization()) .put(JsonKeys.ESTIMATED_SIZE, StepFormatter.translateEstimatedSize(strategy.getEstimatedSize())) .put(JsonKeys.NAME_OF_FIRST_STEP, strategy.getMostPrimaryLeafStep().getDisplayName()) .put(JsonKeys.LEAF_AND_TRANSFORM_STEP_COUNT, strategy.getLeafAndTransformStepCount()) diff --git a/Service/src/main/java/org/gusdb/wdk/service/formatter/UserFormatter.java b/Service/src/main/java/org/gusdb/wdk/service/formatter/UserFormatter.java index 5d93a7d360..96f4e0a381 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/formatter/UserFormatter.java +++ b/Service/src/main/java/org/gusdb/wdk/service/formatter/UserFormatter.java @@ -1,13 +1,11 @@ package org.gusdb.wdk.service.formatter; -import java.util.List; -import java.util.Map; import java.util.Optional; -import org.gusdb.fgputil.accountdb.UserPropertyName; import org.gusdb.wdk.core.api.JsonKeys; import org.gusdb.wdk.model.user.User; import org.gusdb.wdk.model.user.UserPreferences; +import org.gusdb.wdk.model.user.WdkUserProperty; import org.json.JSONException; import org.json.JSONObject; @@ -30,26 +28,26 @@ public class UserFormatter { public static JSONObject getUserJson(User user, boolean isOwner, - Optional userPreferences, List propDefs) throws JSONException { + Optional userPreferences) throws JSONException { JSONObject json = new JSONObject() .put(JsonKeys.ID, user.getUserId()) .put(JsonKeys.IS_GUEST, user.isGuest()); // private fields viewable only by owner if (isOwner) { json.put(JsonKeys.EMAIL, user.getEmail()); - json.put(JsonKeys.PROPERTIES, getPropertiesJson(user.getProfileProperties(), propDefs, isOwner)); + json.put(JsonKeys.PROPERTIES, getPropertiesJson(user, isOwner)); userPreferences.ifPresent(prefs -> json.put(JsonKeys.PREFERENCES, getPreferencesJson(prefs))); } return json; } - private static JSONObject getPropertiesJson(Map props, List propDefs, boolean isOwner) { + private static JSONObject getPropertiesJson(User user, boolean isOwner) { JSONObject propsJson = new JSONObject(); - for (UserPropertyName definedProperty : propDefs) { + for (WdkUserProperty definedProperty : User.USER_PROPERTIES.values()) { if (isOwner || definedProperty.isPublic()) { String key = definedProperty.getName(); - String value = (props.containsKey(key) ? props.get(key) : ""); + String value = Optional.ofNullable(definedProperty.getValue(user)).orElse(""); propsJson.put(key, value); } } diff --git a/Service/src/main/java/org/gusdb/wdk/service/service/AbstractWdkService.java b/Service/src/main/java/org/gusdb/wdk/service/service/AbstractWdkService.java index 392b132dd9..473840151c 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/service/AbstractWdkService.java +++ b/Service/src/main/java/org/gusdb/wdk/service/service/AbstractWdkService.java @@ -38,7 +38,6 @@ import org.gusdb.wdk.model.record.RecordClass; import org.gusdb.wdk.model.record.attribute.AttributeField; import org.gusdb.wdk.model.record.attribute.AttributeFieldContainer; -import org.gusdb.wdk.model.user.UnregisteredUser.UnregisteredUserType; import org.gusdb.wdk.model.user.User; import org.gusdb.wdk.service.UserBundle; @@ -99,10 +98,6 @@ public static String paramToSegment(String pathParamName) { private WdkModel _testWdkModel; - // used to cache a guest user ONLY if one is not present in the session - // NOTE: this is a temporary hack to support Grizzly use of the WDK service - private User _cachedSessionUser; - // public setter for unit tests public void testSetup(WdkModel wdkModel) { _testWdkModel = wdkModel; @@ -145,23 +140,18 @@ protected SessionProxy getSession() { return getRequest().getSession(); } - protected User getSessionUser() { - User user = (User) getRequest().getSession().getAttribute(Utilities.WDK_USER_KEY); + protected User getRequestingUser() { + User user = (User) getRequest().getAttributeMap().get(Utilities.WDK_USER_KEY); if (user != null) { // NOTE: user should ALWAYS be non-null in servlet containers with CheckLoginFilter active return user; } - // used to cache a guest user ONLY if one is not present in the session - // NOTE: this is a temporary hack to support Grizzly use of the WDK service - if (_cachedSessionUser == null) { - _cachedSessionUser = getWdkModel().getUserFactory().createUnregistedUser(UnregisteredUserType.GUEST); - } - return _cachedSessionUser; + throw new IllegalStateException("No user present on request."); } protected boolean isSessionUserAdmin() { List adminEmails = getWdkModel().getModelConfig().getAdminEmails(); - return adminEmails.contains(getSessionUser().getEmail()); + return adminEmails.contains(getRequestingUser().getEmail()); } protected void assertAdmin() { @@ -178,7 +168,7 @@ protected void assertAdmin() { * @throws WdkModelException if error occurs while accessing user data (probably a DB problem) */ protected UserBundle parseTargetUserId(String userIdStr) throws WdkModelException { - return UserBundle.createFromTargetId(userIdStr, getSessionUser(), getWdkModel().getUserFactory(), isSessionUserAdmin()); + return UserBundle.createFromTargetId(userIdStr, getRequestingUser(), getWdkModel().getUserFactory(), isSessionUserAdmin()); } /** diff --git a/Service/src/main/java/org/gusdb/wdk/service/service/AnswerService.java b/Service/src/main/java/org/gusdb/wdk/service/service/AnswerService.java index 74497498be..9010d70ce5 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/service/AnswerService.java +++ b/Service/src/main/java/org/gusdb/wdk/service/service/AnswerService.java @@ -193,8 +193,8 @@ public Response createStandardReportAnswerFromForm(@FormParam("data") String dat public Response createCustomReportAnswer(@PathParam(REPORT_NAME_PATH_PARAM) String reportName, JSONObject body) throws WdkModelException, DataValidationException, RequestMisformatException { AnswerRequest request = parseAnswerRequest(getQuestionOrNotFound(_recordClassUrlSegment, _questionUrlSegment), reportName, - body, getWdkModel(), getSessionUser(), _avoidCacheHit); - return getAnswerResponse(getSessionUser(), request).getSecond(); + body, getWdkModel(), getRequestingUser(), _avoidCacheHit); + return getAnswerResponse(getRequestingUser(), request).getSecond(); } /** diff --git a/Service/src/main/java/org/gusdb/wdk/service/service/QuestionService.java b/Service/src/main/java/org/gusdb/wdk/service/service/QuestionService.java index 4daf4acee7..fc80bf44be 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/service/QuestionService.java +++ b/Service/src/main/java/org/gusdb/wdk/service/service/QuestionService.java @@ -126,7 +126,7 @@ public JSONObject getQuestionNew( @PathParam(SEARCH_PATH_PARAM) String questionUrlSegment) throws WdkModelException { DisplayablyValid validSpec = getDisplayableAnswerSpec( - questionUrlSegment, getWdkModel(), getSessionUser(), name -> getQuestionOrNotFound(_recordClassUrlSegment, name)); + questionUrlSegment, getWdkModel(), getRequestingUser(), name -> getQuestionOrNotFound(_recordClassUrlSegment, name)); JSONObject result = QuestionFormatter.getQuestionJsonWithParams(validSpec, validSpec.get().getValidationBundle()); if (LOG.isDebugEnabled()) LOG.debug("Returning JSON: " + result.toString(2)); return result; @@ -187,7 +187,7 @@ public JSONObject getQuestionRevise( .setQuestionFullName(question.getFullName()) .setParamValues(request.getContextParamValues()) .build( - getSessionUser(), + getRequestingUser(), StepContainer.emptyContainer(), ValidationLevel.SEMANTIC, FillStrategy.NO_FILL); @@ -202,7 +202,7 @@ public JSONObject getQuestionRevise( .setQuestionFullName(question.getFullName()) .setParamValues(request.getContextParamValues()) .build( - getSessionUser(), + getRequestingUser(), StepContainer.emptyContainer(), ValidationLevel.DISPLAYABLE, FillStrategy.FILL_PARAM_IF_MISSING_OR_INVALID) @@ -258,7 +258,7 @@ public JSONArray getQuestionChange(@PathParam(SEARCH_PATH_PARAM) String question .setQuestionFullName(question.getFullName()) .setParamValues(contextParams) .build( - getSessionUser(), + getRequestingUser(), StepContainer.emptyContainer(), ValidationLevel.DISPLAYABLE, FillStrategy.FILL_PARAM_IF_MISSING_OR_INVALID) @@ -337,7 +337,7 @@ public JSONObject getFilterParamOntologyTermSummary( .builder() .putAll(contextParamValues) .buildValidated( - getSessionUser(), + getRequestingUser(), question.getQuery(), StepContainer.emptyContainer(), ValidationLevel.DISPLAYABLE, @@ -392,7 +392,7 @@ public JSONObject getFilterParamSummaryCounts( .builder() .putAll(contextParamValues) .buildValidated( - getSessionUser(), + getRequestingUser(), question.getQuery(), StepContainer.emptyContainer(), ValidationLevel.DISPLAYABLE, diff --git a/Service/src/main/java/org/gusdb/wdk/service/service/RecordService.java b/Service/src/main/java/org/gusdb/wdk/service/service/RecordService.java index c322e3e76e..cb66df3959 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/service/RecordService.java +++ b/Service/src/main/java/org/gusdb/wdk/service/service/RecordService.java @@ -111,7 +111,7 @@ public Response buildResult(@PathParam(RECORD_TYPE_PATH_PARAM) String recordClas RecordRequest request = RecordRequest.createFromJson(recordClass, new JSONObject(body)); // fetch a list of record instances the passed primary key maps to - List records = RecordClass.getRecordInstances(getSessionUser(), request.getPrimaryKey()); + List records = RecordClass.getRecordInstances(getRequestingUser(), request.getPrimaryKey()); // if no mapping exists, return not found if (records.isEmpty()) { diff --git a/Service/src/main/java/org/gusdb/wdk/service/service/SessionService.java b/Service/src/main/java/org/gusdb/wdk/service/service/SessionService.java index 1172ac2144..4544ed0cfd 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/service/SessionService.java +++ b/Service/src/main/java/org/gusdb/wdk/service/service/SessionService.java @@ -24,18 +24,16 @@ import org.gusdb.fgputil.events.Events; import org.gusdb.fgputil.web.CookieBuilder; import org.gusdb.fgputil.web.LoginCookieFactory; -import org.gusdb.fgputil.web.LoginCookieFactory.LoginCookieParts; import org.gusdb.fgputil.web.SessionProxy; -import org.gusdb.oauth2.client.OAuthClient.ValidatedToken; +import org.gusdb.oauth2.client.ValidatedToken; import org.gusdb.wdk.core.api.JsonKeys; -import org.gusdb.wdk.events.NewUserEvent; import org.gusdb.wdk.model.Utilities; import org.gusdb.wdk.model.WdkModel; import org.gusdb.wdk.model.WdkModelException; import org.gusdb.wdk.model.WdkRuntimeException; import org.gusdb.wdk.model.config.ModelConfig; import org.gusdb.wdk.model.config.ModelConfig.AuthenticationMethod; -import org.gusdb.wdk.model.user.UnregisteredUser.UnregisteredUserType; +import org.gusdb.wdk.model.user.BearerTokenUser; import org.gusdb.wdk.model.user.User; import org.gusdb.wdk.service.request.LoginRequest; import org.gusdb.wdk.service.request.exception.RequestMisformatException; @@ -44,12 +42,14 @@ import org.json.JSONException; import org.json.JSONObject; +import com.google.common.net.HttpHeaders; + @Path("/") public class SessionService extends AbstractWdkService { private static final Logger LOG = Logger.getLogger(SessionService.class); - private static final int EXPIRATION_3_YEARS_SECS = 3 * 365 * 24 * 60 * 60; + public static final int EXPIRATION_3_YEARS_SECS = 3 * 365 * 24 * 60 * 60; private static final String REFERRER_HEADER_KEY = "Referer"; @@ -137,8 +137,8 @@ public Response processOauthLogin( String appUrl = getContextUri(); String redirectUrl = isEmpty(originalUrl) ? appUrl : originalUrl; - // Is the user already logged in? - User oldUser = getSessionUser(); + // Was existing bearer token submitted with this request? + User oldUser = getRequestingUser(); if (!oldUser.isGuest()) { return createRedirectResponse(redirectUrl).build(); } @@ -161,11 +161,11 @@ public Response processOauthLogin( throw new WdkModelException("Unable to log in; state token missing, incorrect, or expired."); } - WdkOAuthClientWrapper client = new WdkOAuthClientWrapper(wdkModel); - // Use auth code to get the bearer token, then convert to User - ValidatedToken bearerToken = client.getBearerTokenFromAuth(authCode, appUrl); - User newUser = client.getUserFromValidatedToken(bearerToken, wdkModel.getUserFactory()); + WdkOAuthClientWrapper client = new WdkOAuthClientWrapper(wdkModel); + ValidatedToken bearerToken = client.getBearerTokenFromAuthCode(authCode, appUrl); + User newUser = new BearerTokenUser(wdkModel, client, bearerToken); + wdkModel.getUserFactory().insertUserToUserDb(newUser); // transfer ownership from guest to logged-in user transferOwnership(oldUser, newUser, wdkModel); @@ -200,18 +200,17 @@ public Response processDbLogin(@HeaderParam(REFERRER_HEADER_KEY) String referrer String originalUrl = request.getRedirectUrl(); String redirectUrl = !isEmpty(originalUrl) ? originalUrl : !isEmpty(referrer) ? referrer : appUrl; - // Is the user already logged in? - User oldUser = getSessionUser(); + // Was existing bearer token submitted with this request? + User oldUser = getRequestingUser(); if (!oldUser.isGuest()) { return createRedirectResponse(redirectUrl).build(); } - WdkOAuthClientWrapper client = new WdkOAuthClientWrapper(wdkModel); - // Use passed credentials to get the bearer token, then convert to User + WdkOAuthClientWrapper client = new WdkOAuthClientWrapper(wdkModel); ValidatedToken bearerToken = client.getBearerTokenFromCredentials(request.getEmail(), request.getPassword(), appUrl); - - User newUser = client.getUserFromValidatedToken(bearerToken, wdkModel.getUserFactory()); + User newUser = new BearerTokenUser(wdkModel, client, bearerToken); + wdkModel.getUserFactory().insertUserToUserDb(newUser); // transfer ownership from guest to logged-in user transferOwnership(oldUser, newUser, wdkModel); @@ -259,22 +258,25 @@ private Response getSuccessResponse(ValidatedToken bearerToken, User newUser, Us Events.triggerAndWait(new NewUserEvent(newUser, oldUser, session), new WdkRuntimeException("Unable to complete WDK user assignement.")); - // TODO: leaving legacy code here for reference; remove when deemed appropriate - //LoginCookieFactory baker = new LoginCookieFactory(getWdkModel().getModelConfig().getSecretKey()); - //CookieBuilder loginCookie = baker.createLoginCookie(newUser.getEmail()); + // TODO: until client is updated, must still return WDK login cookie + LoginCookieFactory baker = new LoginCookieFactory(getWdkModel().getModelConfig().getSecretKey()); + CookieBuilder loginCookie = baker.createLoginCookie(newUser.getEmail()); + loginCookie.setMaxAge(EXPIRATION_3_YEARS_SECS); // 3-year expiration (should change secret key before then) - CookieBuilder loginCookie = new CookieBuilder( - LoginCookieFactory.WDK_LOGIN_COOKIE_NAME, + CookieBuilder bearerTokenCookie = new CookieBuilder( + HttpHeaders.AUTHORIZATION, bearerToken.getTokenValue()); - loginCookie.setMaxAge(EXPIRATION_3_YEARS_SECS); + bearerTokenCookie.setMaxAge(EXPIRATION_3_YEARS_SECS); - redirectUrl = getSuccessRedirectUrl(redirectUrl, newUser, loginCookie); + redirectUrl = getSuccessRedirectUrl(redirectUrl, newUser, loginCookie, bearerTokenCookie); return (isRedirectResponse ? createRedirectResponse(redirectUrl) : createJsonResponse(true, null, redirectUrl) - ).cookie(loginCookie.toJaxRsCookie()).build(); + ) + .cookie(loginCookie.toJaxRsCookie(), bearerTokenCookie.toJaxRsCookie()) + .build(); } } @@ -286,7 +288,7 @@ private Response getSuccessResponse(ValidatedToken bearerToken, User newUser, Us * @param cookie login cookie to be sent to the browser * @return page user should be redirected to after successful login */ - protected String getSuccessRedirectUrl(String redirectUrl, User user, CookieBuilder cookie) { + protected String getSuccessRedirectUrl(String redirectUrl, User user, CookieBuilder cookie, CookieBuilder bearerTokenCookie) { return redirectUrl; } @@ -295,12 +297,12 @@ protected String getSuccessRedirectUrl(String redirectUrl, User user, CookieBuil public Response processLogout() throws WdkModelException { // get the current session's user, then invalidate the session - User oldUser = getSessionUser(); + User oldUser = getRequestingUser(); getSession().invalidate(); // get a new session and add new guest user to it SessionProxy session = getSession(); - User newUser = getWdkModel().getUserFactory().createUnregistedUser(UnregisteredUserType.GUEST); + User newUser = getWdkModel().getUserFactory().createUnregistedUser(); session.setAttribute(Utilities.WDK_USER_KEY, newUser); // throw new user event diff --git a/Service/src/main/java/org/gusdb/wdk/service/service/TemporaryResultService.java b/Service/src/main/java/org/gusdb/wdk/service/service/TemporaryResultService.java index 850839f246..c9ed3d22d4 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/service/TemporaryResultService.java +++ b/Service/src/main/java/org/gusdb/wdk/service/service/TemporaryResultService.java @@ -71,7 +71,7 @@ public class TemporaryResultService extends AbstractWdkService { public Response setTemporaryResult(JSONObject requestJson) throws RequestMisformatException, DataValidationException, WdkModelException, ValidationException { AnswerRequest request = parseRequest(requestJson); - String id = TemporaryResultFactory.insertTemporaryResult(getSessionUser().getUserId(), request); + String id = TemporaryResultFactory.insertTemporaryResult(getRequestingUser().getUserId(), request); return Response.ok(new JSONObject().put(ID, id).toString()) .location(getUriInfo().getAbsolutePathBuilder().build(id)).build(); } @@ -109,7 +109,7 @@ private AnswerRequest parseRequest(JSONObject requestJson) .getStepFactory() .getStepById(stepId, ValidationLevel.RUNNABLE) .orElseThrow(() -> new DataValidationException("No step found with ID " + stepId)); - if (step.getUser().getUserId() != getSessionUser().getUserId()) { + if (step.getUser().getUserId() != getRequestingUser().getUserId()) { throw new DataValidationException("No step found with ID " + stepId + " for this user."); } RunnableObj answerSpec = Step.getRunnableAnswerSpec(step.getRunnable() @@ -133,7 +133,7 @@ else if (requestJson.has(SEARCH_NAME) && requestJson.has(SEARCH_CONFIG)){ // parse answer request as we would in answer service return AnswerService.parseAnswerRequest( - question, reporterName, requestJson, getWdkModel(), getSessionUser(), false); + question, reporterName, requestJson, getWdkModel(), getRequestingUser(), false); } else { throw new RequestMisformatException("Either " + STEP_ID + " or (" + diff --git a/Service/src/main/java/org/gusdb/wdk/service/service/search/ColumnReporterService.java b/Service/src/main/java/org/gusdb/wdk/service/service/search/ColumnReporterService.java index 18ea93b8b5..dc2ce835df 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/service/search/ColumnReporterService.java +++ b/Service/src/main/java/org/gusdb/wdk/service/service/search/ColumnReporterService.java @@ -144,7 +144,7 @@ public StreamingOutput runReporter( requestJson.getJSONObject(JsonKeys.SEARCH_CONFIG), toolName, viewFilters); // build answer value - AnswerValue answerValue = AnswerValueFactory.makeAnswer(getSessionUser(), answerSpec); + AnswerValue answerValue = AnswerValueFactory.makeAnswer(getRequestingUser(), answerSpec); // finish configuring the reporter reporter @@ -208,7 +208,7 @@ private RunnableObj makeAnswerSpec(JSONObject answerSpecJson, String // build the spec and return return specBuilder - .build(getSessionUser(), emptyContainer(), RUNNABLE, FILL_PARAM_IF_MISSING) + .build(getRequestingUser(), emptyContainer(), RUNNABLE, FILL_PARAM_IF_MISSING) .getRunnable() .getOrThrow(spec -> new DataValidationException(spec.getValidationBundle())); } diff --git a/Service/src/main/java/org/gusdb/wdk/service/service/user/BasketService.java b/Service/src/main/java/org/gusdb/wdk/service/service/user/BasketService.java index 67fc3dfb23..8c8bf99265 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/service/user/BasketService.java +++ b/Service/src/main/java/org/gusdb/wdk/service/service/user/BasketService.java @@ -283,7 +283,7 @@ public Response createCustomReportAnswer( .builder(getWdkModel()) .setQuestionFullName(recordClass.getRealtimeBasketQuestion().getFullName()) .setViewFilterOptions(AnswerSpecServiceFormat.parseViewFilters(requestJson)) - .buildRunnable(getSessionUser(), StepContainer.emptyContainer()); + .buildRunnable(getRequestingUser(), StepContainer.emptyContainer()); AnswerRequest request = new AnswerRequest(basketAnswerSpec, new AnswerFormatting(reportName, requestJson.getJSONObject(JsonKeys.REPORT_CONFIG)), false); return AnswerService.getAnswerResponse(user, request).getSecond(); @@ -339,7 +339,7 @@ public StreamingOutput getColumnReporterResponse( .builder(getWdkModel()) .setQuestionFullName(recordClass.getRealtimeBasketQuestion().getFullName()) .setViewFilterOptions(AnswerSpecServiceFormat.parseViewFilters(requestJson)) - .buildRunnable(getSessionUser(), StepContainer.emptyContainer()); + .buildRunnable(getRequestingUser(), StepContainer.emptyContainer()); // build and configure the column reporter and stream its result return AnswerService.getAnswerAsStream( diff --git a/Service/src/main/java/org/gusdb/wdk/service/service/user/ProfileService.java b/Service/src/main/java/org/gusdb/wdk/service/service/user/ProfileService.java index b09ad6171e..48fd2ef25d 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/service/user/ProfileService.java +++ b/Service/src/main/java/org/gusdb/wdk/service/service/user/ProfileService.java @@ -51,10 +51,8 @@ public ProfileService(@PathParam(USER_ID_PATH_PARAM) String uid) { // @OutSchema("wdk.users.get-by-id") public JSONObject getById(@QueryParam("includePreferences") Boolean includePreferences) throws WdkModelException { UserBundle userBundle = getUserBundle(Access.PUBLIC); - List propDefs = getWdkModel().getModelConfig() - .getAccountDB().getUserPropertyNames(); return formatUser(userBundle.getTargetUser(), userBundle.isSessionUser(), - getFlag(includePreferences), propDefs); + getFlag(includePreferences)); } /** @@ -67,10 +65,10 @@ public JSONObject getById(@QueryParam("includePreferences") Boolean includePrefe * @return formatted user object * @throws WdkModelException if something goes wrong */ - protected JSONObject formatUser(User user, boolean isSessionUser, boolean includePrefs, List propNames) throws WdkModelException { + protected JSONObject formatUser(User user, boolean isSessionUser, boolean includePrefs) throws WdkModelException { Optional userPrefs = !includePrefs ? Optional.empty() : Optional.of(new UserPreferenceFactory(getWdkModel()).getPreferences(user.getUserId())); - return UserFormatter.getUserJson(user, isSessionUser, userPrefs, propNames); + return UserFormatter.getUserJson(user, isSessionUser, userPrefs); } /** @@ -151,10 +149,6 @@ private Response getProfileUpdateResponse(NewCookie loginCookie) { Response.noContent().cookie(loginCookie).build(); } - private List getPropertiesConfig() { - return getWdkModel().getModelConfig().getAccountDB().getUserPropertyNames(); - } - @PUT @Path("password") @Consumes(MediaType.APPLICATION_JSON) diff --git a/Service/src/main/java/org/gusdb/wdk/service/service/user/StrategyService.java b/Service/src/main/java/org/gusdb/wdk/service/service/user/StrategyService.java index d6f2957aa8..f4f04ac2b1 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/service/user/StrategyService.java +++ b/Service/src/main/java/org/gusdb/wdk/service/service/user/StrategyService.java @@ -287,7 +287,7 @@ public JSONObject duplicateAsBranch(@PathParam(ID_PARAM) long stratId) return new JSONObject().put(JsonKeys.STEP_TREE, StepFormatter.formatAsStepTree( getWdkModel().getStepFactory().copyStrategyToBranch( - getSessionUser(), + getRequestingUser(), getStrategyForCurrentUser(stratId, ValidationLevel.NONE) ), new HashSet() // we don't need to consume the list of step IDs found in the tree @@ -433,7 +433,7 @@ private Strategy applyOverwriteChanges(Strategy strategyToBeOverwritten, long so // copy the source strat's step tree into new, unattached steps JSONObject copiedStepTree = StepFormatter.formatAsStepTree( - getWdkModel().getStepFactory().copyStrategyToBranch(getSessionUser(),sourceStrategy), + getWdkModel().getStepFactory().copyStrategyToBranch(getRequestingUser(),sourceStrategy), new HashSet() // we don't need to consume the list of step IDs found in the tree ); diff --git a/Service/src/main/java/org/gusdb/wdk/service/service/user/UserDatasetService.java b/Service/src/main/java/org/gusdb/wdk/service/service/user/UserDatasetService.java index f7720d5230..92d3e8d0a3 100644 --- a/Service/src/main/java/org/gusdb/wdk/service/service/user/UserDatasetService.java +++ b/Service/src/main/java/org/gusdb/wdk/service/service/user/UserDatasetService.java @@ -299,7 +299,7 @@ private long parseLongId(String idStr, RuntimeException exception) { */ private void validateTargetUserIds(Set targetUserIds) throws WdkModelException { for(Long targetUserId : targetUserIds) { - UserBundle targetUserBundle = UserBundle.createFromTargetId(targetUserId.toString(), getSessionUser(), getWdkModel().getUserFactory(), isSessionUserAdmin()); + UserBundle targetUserBundle = UserBundle.createFromTargetId(targetUserId.toString(), getRequestingUser(), getWdkModel().getUserFactory(), isSessionUserAdmin()); if (!targetUserBundle.isValidUserId()) { LOG.error("This user dataset share service request contains the following invalid user: " + targetUserId); throw new NotFoundException(formatNotFound(UserService.USER_RESOURCE + targetUserBundle.getTargetUserIdString()));