package org.karmaexchange.resources;
import static org.karmaexchange.util.OfyService.ofy;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.annotation.XmlRootElement;
import org.karmaexchange.auth.AuthProvider;
import org.karmaexchange.auth.AuthProvider.CredentialVerificationResult;
import org.karmaexchange.auth.AuthProvider.UserInfo;
import org.karmaexchange.auth.AuthProviderCredentials;
import org.karmaexchange.auth.AuthProviderType;
import org.karmaexchange.auth.GlobalUidMapping;
import org.karmaexchange.auth.Session;
import org.karmaexchange.dao.User;
import org.karmaexchange.dao.UserUsage;
import org.karmaexchange.resources.msg.ErrorResponseMsg;
import org.karmaexchange.resources.msg.ErrorResponseMsg.ErrorInfo;
import com.googlecode.objectify.Key;
import lombok.Data;
import lombok.NoArgsConstructor;
@Path(AuthResource.RESOURCE_PATH)
@NoArgsConstructor
public class AuthResource {
/*
* All methods in this class are invoked with the admin user key. This enables us to
* create and load any user object.
*/
public static final String RESOURCE_PATH = "/auth";
@Path("login")
@POST
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response login(LoginRequest loginRequest,
@Context HttpServletRequest req) {
// 1. Verify the credentials
AuthProvider authProvider =
loginRequest.getProviderType().getProvider();
CredentialVerificationResult verificationResult =
authProvider.verifyUserCredentials(loginRequest.getCredentials(), req);
// 2. Lookup the user based on the credentials.
GlobalUidMapping userMapping = GlobalUidMapping.load(verificationResult.getGlobalUid());
User user;
if (userMapping == null) {
// If a user doesn't exist create one.
user = createUserAndMapping(authProvider, verificationResult);
} else {
user = ofy().load().key(userMapping.getUserKey()).now();
if (user == null) {
// It's possible that the user has been deleted. If so, create a new user.
user = createUserAndMapping(authProvider, verificationResult);
}
}
// 3. Create a new session for the user.
Session session = new Session(Key.create(user));
ofy().save().entity(session); // Asynchronously save the session.
// 4. Track access.
UserUsage.trackAccess(verificationResult.getGlobalUid(), user);
// 5. Return the login response.
return Response.ok(user)
.cookie(session.getCookie())
.build();
}
/*
* Extends an existing non-expired session by creating a new session with the default
* number of hours before it expires.
*
* Renew is a quick workaround to implementing the full session expiration logic. Sessions
* are renewed the first time the app is loaded. App load time is an easy time to renew because
* there is no concern about in-flight requests. The app still has to handle sessions that
* are expired when the app is loaded.
*/
@Path("renew")
@POST
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response renew(@Context HttpServletRequest req) {
Session oldSession = Session.getCurrentSession(req);
// Handle the case where there is no cookie.
if (oldSession == null) {
throw ErrorResponseMsg.createException("Session has expired",
ErrorInfo.Type.SESSION_EXPIRED);
}
User user = ofy().load().key(oldSession.getUserKey()).now();
if (user == null) {
throw ErrorResponseMsg.createException("User no longer exists for session",
ErrorInfo.Type.SESSION_EXPIRED);
}
// Create the new session
Session newSession = new Session(oldSession.getUserKey());
ofy().save().entity(newSession); // Asynchronously save the session.
// Delete the old session
ofy().delete().entity(oldSession);
return Response.ok(user)
.cookie(newSession.getCookie())
.build();
}
@Path("logout")
@POST
public Response logout(@Context HttpServletRequest req) {
// Delete the key so it can not be used by anyone else.
Key<Session> sessionKey = Session.getCurrentSessionKey(req);
if (sessionKey != null) {
ofy().delete().key(sessionKey);
}
return Response.ok()
.cookie(Session.LOGOUT_COOKIE)
.build();
}
private User createUserAndMapping(AuthProvider authProvider,
CredentialVerificationResult verificationResult) {
UserInfo userInfo = authProvider.createUser(verificationResult);
Key<User> userKey = User.upsertNewUser(userInfo);
// Now that the user has been persisted, get the persisted version of the user.
User user = ofy().load().key(userKey).now();
// TODO(avaliani): handle orphaned user objects. Until there is a mapping to the user
// the user object is not retrievable.
GlobalUidMapping mapping =
new GlobalUidMapping(verificationResult.getGlobalUid(), Key.create(user));
ofy().save().entity(mapping); // Asynchronously save the new mapping
return user;
}
@XmlRootElement
@Data
private static class LoginRequest {
private AuthProviderType providerType;
private AuthProviderCredentials credentials;
}
}