diff --git a/src/prerna/semoss/web/services/config/ServerConfigurationResource.java b/src/prerna/semoss/web/services/config/ServerConfigurationResource.java index 62b71bd4..427f1b3d 100644 --- a/src/prerna/semoss/web/services/config/ServerConfigurationResource.java +++ b/src/prerna/semoss/web/services/config/ServerConfigurationResource.java @@ -256,24 +256,28 @@ private static void loadConfig() { private static Map getConfiguration(@Context HttpServletRequest request, User user) { HttpSession session = request.getSession(); - Map myConfiguration = new HashMap<>(); - myConfiguration.putAll(config); + Map clientConfig = new HashMap<>(); + clientConfig.putAll(config); // session timeout // in case we have different timeout for the admin // we have this grab for this session what the timeout value is - myConfiguration.put("timeout", (double) session.getMaxInactiveInterval() / 60); + clientConfig.put("timeout", (double) session.getMaxInactiveInterval() / 60); // append values that can change without restarting the server // logins allowed - myConfiguration.put("loginsAllowed", SocialPropertiesUtil.getInstance().getLoginsAllowed()); + clientConfig.put("loginsAllowed", SocialPropertiesUtil.getInstance().getLoginsAllowed()); + // connections allowed + clientConfig.put("connectionsAllowed", SocialPropertiesUtil.getInstance().getConnectionsAllowed()); // get a list of all the logins and the display name and if it is oauth - myConfiguration.put("availableProviders", SocialPropertiesUtil.getInstance().getAvailableProviders()); + clientConfig.put("availableProviders", SocialPropertiesUtil.getInstance().getAvailableProviders()); + // get a list of all the resource providers and the display name if it is oauth + clientConfig.put("availableResourceProviders", + SocialPropertiesUtil.getInstance().getAvailableResourceProviders()); // is native registration allowed - myConfiguration.put("nativeRegistration", SocialPropertiesUtil.getInstance().isNativeRegistrationAllowed()); + clientConfig.put("nativeRegistration", SocialPropertiesUtil.getInstance().isNativeRegistrationAllowed()); // password requirements try { - myConfiguration.put("passwordRequirements", - PasswordRequirements.getInstance().getAllPasswordRequirements()); + clientConfig.put("passwordRequirements", PasswordRequirements.getInstance().getAllPasswordRequirements()); } catch (Exception e) { classLogger.error( "Failed to load password requirements; 'passwordRequirements' will be omitted from server config", @@ -284,29 +288,32 @@ private static Map getConfiguration(@Context HttpServletRequest // TODO: should move away from logins cause sometimes people are using this as // if the name is the ID // TODO: but not sure where this is all happening, so sending both keys for now - myConfiguration.put("logins", User.getLoginNames(user)); - myConfiguration.put("loginDetails", User.getLoginDetails(user)); + clientConfig.put("logins", User.getLoginNames(user)); + clientConfig.put("loginDetails", User.getLoginDetails(user)); + // current resource connections + clientConfig.put("connections", User.getConnectionsNames(user)); + clientConfig.put("connectionDetails", User.getConnectionDetails(user)); // themes - myConfiguration.put("theme", AdminThemeUtils.getActiveAdminTheme()); + clientConfig.put("theme", AdminThemeUtils.getActiveAdminTheme()); // add if we are using csrf - myConfiguration.put("csrf", Boolean.parseBoolean(session.getAttribute("csrf") + "")); + clientConfig.put("csrf", Boolean.parseBoolean(session.getAttribute("csrf") + "")); // add metakey options - myConfiguration.put("databaseMetaKeys", SecurityEngineUtils.getMetakeyOptions(null)); - myConfiguration.put("engineMetaKeys", SecurityEngineUtils.getMetakeyOptions(null)); - myConfiguration.put("projectMetaKeys", SecurityProjectUtils.getMetakeyOptions(null)); - myConfiguration.put("insightMetaKeys", SecurityInsightUtils.getMetakeyOptions(null)); - myConfiguration.put("userMetaKeys", SecurityUserUtils.getMetakeyOptions(null)); - myConfiguration.put("notificationEnabled", Utility.isNotificationDatabaseEnabled()); - myConfiguration.put("auditLogEnabled", Utility.isAuditLogsDatabaseEnabled()); + clientConfig.put("databaseMetaKeys", SecurityEngineUtils.getMetakeyOptions(null)); + clientConfig.put("engineMetaKeys", SecurityEngineUtils.getMetakeyOptions(null)); + clientConfig.put("projectMetaKeys", SecurityProjectUtils.getMetakeyOptions(null)); + clientConfig.put("insightMetaKeys", SecurityInsightUtils.getMetakeyOptions(null)); + clientConfig.put("userMetaKeys", SecurityUserUtils.getMetakeyOptions(null)); + clientConfig.put("notificationEnabled", Utility.isNotificationDatabaseEnabled()); + clientConfig.put("auditLogEnabled", Utility.isAuditLogsDatabaseEnabled()); // current date - myConfiguration.put("systemDate", new SemossDate(Utility.getCurrentZonedDateTimeUTC())); + clientConfig.put("systemDate", new SemossDate(Utility.getCurrentZonedDateTimeUTC())); // do not keep this session // if no user and it is new if (user == null && (session.isNew() || request.isRequestedSessionIdValid())) { session.invalidate(); } - return myConfiguration; + return clientConfig; } @GET diff --git a/src/prerna/semoss/web/services/local/UserResource.java b/src/prerna/semoss/web/services/local/UserResource.java index 5215bc8e..6bd4a194 100644 --- a/src/prerna/semoss/web/services/local/UserResource.java +++ b/src/prerna/semoss/web/services/local/UserResource.java @@ -146,6 +146,34 @@ public Response getAllLogins(@Context HttpServletRequest request) { Map retMap = User.getLoginNames(semossUser); return WebUtility.getResponse(retMap, 200, newCookies.toArray(new NewCookie[] {})); } + /** + * Returns the active connection providers for the current session user. + * + * @param request inbound HTTP request + * @return provider-name map response + */ + @GET + @Path("/connections") + public Response getAllConnections(@Context HttpServletRequest request) { + List newCookies = new ArrayList<>(); + HttpSession session = request.getSession(false); + User semossUser = null; + if (session != null) { + semossUser = (User) session.getAttribute(Constants.SESSION_USER); + } + + if (semossUser == null) { + // not authenticated + // remove any cookies we shouldn't have + WebUtility.expireSessionCookies(request, newCookies); + + if (session != null && (session.isNew() || request.isRequestedSessionIdValid())) { + session.invalidate(); + } + } + Map retMap = User.getConnectionsNames(semossUser); + return WebUtility.getResponse(retMap, 200, newCookies.toArray(new NewCookie[] {})); + } /** * Logs a user out of one provider or all providers in the current session. @@ -185,21 +213,33 @@ public Response logout(@PathParam("provider") String provider, noUser = true; } else { AuthProvider token = AuthProvider.valueOf(provider.toUpperCase()); - String assetEngineId = null; - - // TODO: what does this part do? - // TODO: feel like when logout need to adjust the asset id - if (thisUser.getLogins().size() == 1) { - thisUser.getAssetProjectId(token); + boolean loginToken = thisUser.getAccessToken(token) != null; + boolean resourceToken = thisUser.getResourceAccessToken(token) != null; + + if (resourceToken) { + thisUser.dropResourceAccessToken(token); + removed = true; + if (!loginToken) { + session.setAttribute(Constants.SESSION_USER, thisUser); + } } - removed = thisUser.dropAccessToken(token); - if (thisUser.getLogins().isEmpty()) { - noUser = true; - } else { - request.getSession().setAttribute(Constants.SESSION_USER, thisUser); - Thread.ofVirtual().start(new SyncUserAssetsThread(assetEngineId)); - // put the new map for the user space - session.setAttribute(Constants.USER_ASSET_IDS, thisUser.getAssetEngineMap()); + if (loginToken) { + String assetEngineId = null; + + // TODO: what does this part do? + // TODO: feel like when logout need to adjust the asset id + if (thisUser.getLogins().size() == 1) { + thisUser.getAssetProjectId(token); + } + removed = thisUser.dropAccessToken(token) || removed; + if (thisUser.getLogins().isEmpty()) { + noUser = true; + } else { + session.setAttribute(Constants.SESSION_USER, thisUser); + Thread.ofVirtual().start(new SyncUserAssetsThread(assetEngineId)); + // put the new map for the user space + session.setAttribute(Constants.USER_ASSET_IDS, thisUser.getAssetEngineMap()); + } } } @@ -263,6 +303,28 @@ public Response logout(@PathParam("provider") String provider, return WebUtility.getResponseNoCache(ret, 200, nullCookies.toArray(new NewCookie[] {})); } + /** + * Adds or updates a resource access token in the current user session. + * + * @param token normalized access token + * @param request inbound HTTP request + */ + public static void addResourceAccessToken(AccessToken token, HttpServletRequest request) { + HttpSession session = request.getSession(); + User semossUser = (User) session.getAttribute(Constants.SESSION_USER); + if (semossUser == null) { + classLogger.error("No user found in session when trying to add resource access token"); + return; + } + + // add new resource access token to the user + semossUser.setResourceAccessToken(token); + semossUser.setAnonymous(false); + session.setAttribute(Constants.SESSION_USER, semossUser); + + // log the user resource connection + classLogger.info("User is connected to provider " + token.getProvider()); + } /** * Adds or updates an authenticated access token in the current user session. @@ -455,6 +517,17 @@ private User refreshSessionUser(HttpServletRequest request, HttpSession session) private boolean hasAccessToken(User user, AuthProvider provider) { return user != null && user.getAccessToken(provider) != null; } + + /** + * Checks whether a user has a resource access token for the requested provider. + * + * @param user user to inspect + * @param provider authentication provider + * @return {@code true} when a token exists + */ + private boolean hasResourceAccessToken(User user, AuthProvider provider) { + return user != null && user.getResourceAccessToken(provider) != null; + } /** * Encodes the request query string for safe OAuth parsing. @@ -1089,6 +1162,16 @@ public Response loginTwitter(@Context HttpServletRequest request, @Context HttpS return loginGeneric("twitter", request, response); } + /** + * Checks whether the current session already has an authenticated login user. + * + * @param user session user to inspect + * @return {@code true} when the session user exists and has at least one login + */ + private boolean hasLoggedInUser(User user) { + return user != null && user.getLogins() != null && !user.getLogins().isEmpty(); + } + /** * Handles OAuth login callback flow for a generic provider. */ @@ -1098,9 +1181,12 @@ public Response loginTwitter(@Context HttpServletRequest request, @Context HttpS public Response loginGeneric(@PathParam("provider") String provider, @Context HttpServletRequest request, @Context HttpServletResponse response) throws IOException { provider = WebUtility.inputSanitizer(provider); - if (socialData.getLoginsAllowed().get(provider) == null || !socialData.getLoginsAllowed().get(provider)) { - Map ret = new HashMap<>(); - ret.put(Constants.ERROR_MESSAGE, provider + " login is not allowed"); + boolean isProviderLoginAllowed = socialData.getLoginsAllowed().getOrDefault(provider.toLowerCase(), false); + boolean isProviderConnectorAllowed = socialData.getConnectionsAllowed().getOrDefault(provider.toLowerCase(), + false); + if (!isProviderLoginAllowed && !isProviderConnectorAllowed) { + Map ret = new HashMap<>(); + ret.put(Constants.ERROR_MESSAGE, "Login/Connect with " + provider + " is not allowed"); return WebUtility.getResponse(ret, 400); } @@ -1118,9 +1204,21 @@ public Response loginGeneric(@PathParam("provider") String provider, @Context Ht HttpSession session = initializeOAuthLoginSession(request); User userObj = getSessionUser(session); + boolean useLoginFlow = isProviderLoginAllowed; + boolean useConnectorFlow = !useLoginFlow && isProviderConnectorAllowed; + if (useConnectorFlow && !hasLoggedInUser(userObj)) { + // A logged-in user is required to connect an external account, so if the + // session user is null or has no logins, return an error. + Map ret = new HashMap<>(); + ret.put(Constants.ERROR_MESSAGE, + "Log in with a native or other social account before connecting " + provider); + return WebUtility.getResponse(ret, 401); + } + boolean needsLogin = useLoginFlow && !hasAccessToken(userObj, providerEnum); + boolean needsConnector = useConnectorFlow && !hasResourceAccessToken(userObj, providerEnum); String queryString = getEncodedQueryString(request); if (hasOAuthCode(queryString)) { - if (!hasAccessToken(userObj, providerEnum)) { + if (needsLogin || needsConnector) { String[] outputs = HttpHelperUtility.getCodes(queryString); // oauth code should match [ -~]+ (1 or more ascii) @@ -1150,7 +1248,14 @@ public Response loginGeneric(@PathParam("provider") String provider, @Context Ht // fill user groups filler.fillUserGroups(accessToken, prefix); - addAccessToken(accessToken, request, autoAdd); + if (needsLogin) { + // add the access token to the session user (creating a new user if needed) + addAccessToken(accessToken, request, autoAdd); + } else if (needsConnector) { + // add the access token to the session user (no auto-add since the user must + // already exist) + addResourceAccessToken(accessToken, request); + } classLogger.debug("Access Token is.. {}", accessToken.getAccess_token()); } @@ -1158,7 +1263,7 @@ public Response loginGeneric(@PathParam("provider") String provider, @Context Ht } userObj = refreshSessionUser(request, session); - if (!hasAccessToken(userObj, providerEnum)) { + if (needsLogin || needsConnector) { // not authenticated response.setStatus(302); response.sendRedirect(buildProviderAuthorizeRedirect(filler, prefix, session)); @@ -1638,6 +1743,17 @@ public Response createAPIUser(@Context HttpServletRequest request) { ////////////////////////////////////////////////////////////////////// + /** + * Returns the map of enabled and disabled connection providers. + */ + + @GET + @Produces("application/json") + @Path("/connectionsAllowed/") + public Response connectionsAllowed(@Context HttpServletRequest request) { + return WebUtility.getResponse(socialData.getConnectionsAllowed(), 200); + } + /** * Returns the map of enabled and disabled login providers. the FE */