Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Model/src/main/java/org/gusdb/wdk/core/api/JsonKeys.java
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ public class JsonKeys {
public static final String PREFERENCES = "preferences";
public static final String GLOBAL = "global";
public static final String PROJECT = "project";
public static final String BEARER_TOKEN = "bearerToken";

// date and date range keys
public static final String MIN_DATE = "minDate";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,9 @@ private String findRawBearerToken(RequestData request, ContainerRequestContext r
}

protected boolean isPathToSkip(String path) {
// skip user check for prometheus metrics requests
return SystemService.PROMETHEUS_ENDPOINT_PATH.equals(path);
// skip user check for prometheus metrics and new guest requests
return SystemService.PROMETHEUS_ENDPOINT_PATH.equals(path)
|| SessionService.CREATE_GUEST_ENDPOINT_PATH.equals(path);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import java.util.Set;
import java.util.UUID;

import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
Expand All @@ -23,6 +25,7 @@
import org.gusdb.fgputil.EncryptionUtil;
import org.gusdb.fgputil.FormatUtil;
import org.gusdb.fgputil.Tuples.TwoTuple;
import org.gusdb.fgputil.functional.Either;
import org.gusdb.fgputil.web.CookieBuilder;
import org.gusdb.fgputil.web.LoginCookieFactory;
import org.gusdb.oauth2.client.ValidatedToken;
Expand All @@ -41,13 +44,22 @@
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);

public static final String CREATE_GUEST_ENDPOINT_PATH = "create-guest";

private enum ResponseType {
REDIRECT, JSON;
}

private static class UserTupleEither extends Either<TwoTuple<ValidatedToken, User>, String> {
public UserTupleEither(TwoTuple<ValidatedToken, User> userTuple) { super(userTuple, null); }
public UserTupleEither(String errorMessage) { super(null, errorMessage); }
}

public static final int EXPIRATION_3_YEARS_SECS = 3 * 365 * 24 * 60 * 60;

private static final String REFERRER_HEADER_KEY = "Referer";
Expand Down Expand Up @@ -106,6 +118,14 @@ private static String generateStateToken(WdkModel wdkModel) throws WdkModelExcep
return EncryptionUtil.encrypt(saltedString);
}

@GET
@Path(CREATE_GUEST_ENDPOINT_PATH)
@Produces(MediaType.APPLICATION_JSON)
public Response createGuest() throws WdkModelException {
TwoTuple<ValidatedToken, User> guest = getWdkModel().getUserFactory().createUnregisteredUser();
return getSuccessResponse(guest, null, ResponseType.JSON);
}

@GET
@Path("login")
public Response processOauthLogin(
Expand All @@ -128,7 +148,8 @@ public Response processOauthLogin(
// Was existing bearer token submitted with this request?
User oldUser = getRequestingUser();
if (!oldUser.isGuest()) {
return createRedirectResponse(redirectUrl).build();
// TODO: should this be 409? Work it out with UI
throw new BadRequestException("Only guests can log in");
}

try {
Expand Down Expand Up @@ -157,12 +178,19 @@ public Response processOauthLogin(
// transfer ownership from guest to logged-in user
transferOwnership(oldUser, newUser, wdkModel);

// determine response type
ResponseType responseType = getHeaders()
.get(HttpHeaders.ACCEPT)
.stream().findFirst()
.map(val -> val.equals(MediaType.APPLICATION_JSON) ? ResponseType.JSON : ResponseType.REDIRECT)
.orElse(ResponseType.REDIRECT);

// login successful; create redirect response
return getSuccessResponse(bearerToken, newUser, oldUser, redirectUrl, true);
return getSuccessResponse(new TwoTuple<>(bearerToken, newUser), redirectUrl, responseType);
}
catch (InvalidPropertiesException ex) {
LOG.error("Could not authenticate user's identity. Exception thrown: ", ex);
return createJsonResponse(false, "Invalid auth token or redirect URI", null).build();
return createJsonResponse(new UserTupleEither("Invalid auth token or redirect URI"), null);
}
catch (Exception ex) {
LOG.error("Unsuccessful login attempt: " + ex.getMessage(), ex);
Expand Down Expand Up @@ -206,15 +234,16 @@ public Response processDbLogin(@HeaderParam(REFERRER_HEADER_KEY) String referrer
// transfer ownership from guest to logged-in user
transferOwnership(oldUser, newUser, wdkModel);

return getSuccessResponse(bearerToken, newUser, oldUser, redirectUrl, false);
// return success JSON response
return getSuccessResponse(new TwoTuple<>(bearerToken, newUser), redirectUrl, ResponseType.JSON);

}
catch (JSONException e) {
throw new RequestMisformatException(e.getMessage());
}
catch (InvalidPropertiesException ex) {
LOG.error("Could not authenticate user's identity. Exception thrown: ", ex);
return createJsonResponse(false, "Invalid username or password", null).build();
return createJsonResponse(new UserTupleEither("Invalid username or password"), null);
}
}

Expand All @@ -231,33 +260,33 @@ protected void transferOwnership(User oldUser, User newUser, WdkModel wdkModel)
*
* @param bearerToken bearer token for the new user
* @param newUser newly logged in user
* @param oldUser user previously on session, if any
* @param redirectUrl incoming original page
* @param isRedirectResponse whether to return redirect or JSON response
* @return success response
* @throws WdkModelException
*/
private Response getSuccessResponse(ValidatedToken bearerToken, User newUser, User oldUser,
String redirectUrl, boolean isRedirectResponse) throws WdkModelException {
private Response getSuccessResponse(TwoTuple<ValidatedToken, User> newUser,
String redirectUrl, ResponseType responseType) throws WdkModelException {

// only using this to synchronize on the user
TemporaryUserData tmpData = getTemporaryUserData();

synchronized(tmpData) {

// 3-year expiration (should change secret key before then)
// FIXME: cookie sending should be removed/deleted once client has support for header/response body transmission
CookieBuilder bearerTokenCookie = new CookieBuilder(
HttpHeaders.AUTHORIZATION,
bearerToken.getTokenValue());
newUser.getFirst().getTokenValue());
bearerTokenCookie.setMaxAge(EXPIRATION_3_YEARS_SECS);

redirectUrl = getSuccessRedirectUrl(redirectUrl, newUser, bearerTokenCookie);
redirectUrl = getSuccessRedirectUrl(redirectUrl, newUser.getSecond(), bearerTokenCookie);

return (isRedirectResponse ?
createRedirectResponse(redirectUrl) :
createJsonResponse(true, null, redirectUrl)
)
.cookie(bearerTokenCookie.toJaxRsCookie())
.build();
switch(responseType) {
case REDIRECT: return createRedirectResponse(redirectUrl).cookie(bearerTokenCookie.toJaxRsCookie()).build();
case JSON: return createJsonResponse(new UserTupleEither(newUser), redirectUrl);
default: throw new IllegalStateException(); // should never happen
}
}
}

Expand Down Expand Up @@ -316,19 +345,31 @@ public static NewCookie getAuthCookie(String tokenValue) {
}

/**
* Convenience method to set up a response builder that returns JSON containing request result
* Creates a JSON response for requests served by this class
*
* @param success whether the login was successful
* @param message a failure message if not successful
* @param redirectUrl url to which to redirect the user if successful
* @return partially constructed response
* @param userTupleOrErrorMessage Either object containing a token/user (success case) or error message
* @param redirectUrl URL to which use should eventually be redirected
* @return JSON response
*/
private static ResponseBuilder createJsonResponse(boolean success, String message, String redirectUrl) {
return Response.ok(new JSONObject()
.put(JsonKeys.SUCCESS, success)
.put(JsonKeys.MESSAGE, message)
.put(JsonKeys.REDIRECT_URL, redirectUrl)
.toString());
private static Response createJsonResponse(UserTupleEither userTupleOrErrorMessage, String redirectUrl) {
JSONObject json = new JSONObject()
.put(JsonKeys.SUCCESS, userTupleOrErrorMessage.isLeft())
.put(JsonKeys.REDIRECT_URL, redirectUrl);
userTupleOrErrorMessage
.ifLeft(userTuple ->
json
.put(JsonKeys.BEARER_TOKEN, userTuple.getFirst().getTokenValue())
.put(JsonKeys.USER_ID, userTuple.getSecond().getUserId())
.put(JsonKeys.IS_GUEST, userTuple.getSecond().isGuest())
)
.ifRight(errorMessage ->
json
.put(JsonKeys.MESSAGE, errorMessage)
);
return Response
.ok(json.toString())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.build();
}

/**
Expand Down
Loading