package elw.web;
import elw.dao.Auth;
import elw.dao.QueriesImpl;
import elw.miniweb.Message;
import elw.vo.Admin;
import elw.vo.EmailAuth;
import elw.vo.Group;
import elw.vo.Student;
import elw.web.core.W;
import elw.webauth.AuthException;
import elw.webauth.ControllerAuth;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
/**
* Adding elw-specific auth handling.
*/
@Controller
@RequestMapping("/auth/**/*")
public class ControllerAuthElw extends ControllerAuth {
private final QueriesImpl queries;
private final ElwServerConfig elwServerConfig;
private final Random random;
public ControllerAuthElw(
final ElwServerConfig serverConfig,
final QueriesImpl queries
) {
super(serverConfig);
this.elwServerConfig = serverConfig;
this.queries = queries;
this.random = new Random();
}
@RequestMapping(
value = "nameresponse",
method = RequestMethod.POST
)
public ModelAndView do_nameresponseGet(
final HttpServletRequest req,
final HttpServletResponse resp
) throws IOException {
final String nameFirstParam = req.getParameter("name_first");
if (nameFirstParam == null || nameFirstParam.trim().length() == 0) {
resp.sendError(
HttpServletResponse.SC_BAD_REQUEST,
"first name not set"
);
return null;
}
final String nameFirst = nameFirstParam.trim();
final String nameLastParam = req.getParameter("name_last");
if (nameLastParam == null || nameLastParam.trim().length() == 0) {
resp.sendError(
HttpServletResponse.SC_BAD_REQUEST,
"last name not set"
);
return null;
}
final String nameLast = nameLastParam.trim();
final String nameFull = nameLast + ' ' + nameFirst;
for (Admin admin : queries.admins()) {
if (admin.getName().equalsIgnoreCase(nameFull)) {
Message.addWarn(req, "Logged in as " + admin.getName() + ": READ-ONLY");
authAdm(
req,
admin,
Collections.<String>emptyList(),
Collections.<String>emptyList()
);
return processAuthSuccess(req, resp);
}
}
final List<Group> groups = queries.groups();
for (Group group : groups) {
for (Student student : group.getStudents().values()) {
if (student.getName().equalsIgnoreCase(nameFull)) {
final Auth auth = ControllerElw.auth(req);
if (auth != null && auth.isAdm() && auth.isVerified()) {
// impersonate, leaving admin credentials intact
Message.addInfo(req, "impersonated as " + student.getName());
auth.setGroup(group);
auth.setStudent(student);
return processAuthSuccess(req, resp);
}
if (Auth.isVerificationSetupEmpty(student)) {
Message.addInfo(req, "Logged in as " + student.getName() + ", no extra verifications required");
} else {
Message.addWarn(req, "Logged in as " + student.getName() + ": READ-ONLY");
if (!Auth.isEmailEmpty(student)) {
Message.addWarn(req, "Log-in via email " + student.getEmail() + " to gain full access");
}
if (!student.getOpenIds().isEmpty()) {
Message.addWarn(req, "Log-in via OpenID " + student.getOpenIds() + " to gain full access");
}
}
authStudent(
req, student, group,
Collections.<String>emptyList(),
Collections.<String>emptyList()
);
return processAuthSuccess(req, resp);
}
}
}
Message.addWarn(req, "No users found, please check your spelling?");
resp.sendRedirect(elwServerConfig.getBaseUrl() + "auth.html");
return null;
}
@Override
protected String registerChallengeToken(
final HttpServletRequest req, String emailAddress
) throws AuthException {
checkEmailRegistered(emailAddress);
final long targetDelayMillis =
elwServerConfig.getMailTargetDelayMillis();
final EmailAuth newAuth;
synchronized (ControllerAuthElw.class) {
final EmailAuth emailAuth = queries.lastAuth(emailAddress);
if (emailAuth != null) {
final long emailEnableMillis =
emailAuth.getStamp() + targetDelayMillis;
if (emailEnableMillis > System.currentTimeMillis()) {
throw new AuthException(
"mailing " + emailAddress + " too often",
new IllegalStateException("email recently sent")
);
}
}
newAuth = queries.createAuth(emailAddress);
}
return challengeToken(
newAuth.getEmail(),
newAuth.getStamp(),
req.getSession(true).getId()
);
}
protected void checkEmailRegistered(final String emailAddress) throws AuthException {
try {
final Admin admin = queries.adminSome(emailAddress);
if (admin != null) {
if (!admin.getOpenIds().isEmpty()) {
throw new AuthException(
"No such email OR OpenID is your way",
new IllegalStateException("OpenID is preferred over SMTP")
);
}
return;
}
final List<Group> groups = queries.groups();
for (Group group : groups) {
for (Student student : group.getStudents().values()) {
if (student.getEmail().equalsIgnoreCase(emailAddress)) {
if (student.getOpenIds().isEmpty()) {
return;
}
throw new AuthException(
"No such email OR OpenID is your way",
new IllegalStateException(
"OpenID is preferred over SMTP"
)
);
}
}
}
} finally {
// probing admin emails via timing attack, hmmmm?..
try {
Thread.sleep(1000 + random.nextInt(500));
} catch (InterruptedException e) {
// ignored
}
}
throw new AuthException(
"No such email OR OpenID is your way",
new IllegalStateException(
"No users with such an email"
)
);
}
@Override
protected boolean activateAuth(
final HttpServletRequest req,
final String email,
final String token
) throws AuthException {
final EmailAuth emailAuth = queries.lastAuth(email);
if (emailAuth == null) {
return false;
}
if (emailAuth.isActivated()) {
return false;
}
final long respTimeout = elwServerConfig.getMailResponseTimeoutMillis();
if (emailAuth.getStamp() + respTimeout < System.currentTimeMillis()) {
return false;
}
final String challengeToken = challengeToken(
email,
emailAuth.getStamp(),
req.getSession(true).getId()
);
if (challengeToken.equals(token)) {
queries.activateAuth(emailAuth);
return true;
}
return false;
}
@Override
protected void processAuthInfo(
final HttpServletRequest req,
final HttpSession session,
final List<String> emails,
final List<String> openIds
) {
Message.addInfo(req, "openIds: " + openIds + " emails: " + emails);
for (String email : emails) {
final Admin admin = queries.adminSome(email);
if (admin != null) {
authAdm(
req,
admin,
Collections.singletonList(email),
intersect(openIds, admin.getOpenIds())
);
return;
}
}
for (Admin admin : queries.admins()) {
final List<String> commonOpenIds =
intersect(openIds, admin.getOpenIds());
if (!commonOpenIds.isEmpty()) {
authAdm(
req,
admin,
openIds.size() == 1 ? emails : Collections.<String>emptyList(),
commonOpenIds
);
return;
}
}
final List<Group> groups = queries.groups();
for (Group group : groups) {
for (Student student : group.getStudents().values()) {
final List<String> commonOpenIds =
intersect(openIds, student.getOpenIds());
if (!commonOpenIds.isEmpty()) {
authStudent(
req,
student,
group,
openIds.size() == 1 ? emails : Collections.<String>emptyList(),
commonOpenIds
);
return;
}
boolean emailPresent = false;
for (String email : emails) {
if (email.equalsIgnoreCase(student.getEmail())) {
emailPresent = true;
break;
}
}
if (emailPresent) {
authStudent(
req,
student,
group,
Collections.singletonList(student.getEmail()),
commonOpenIds
);
return;
}
}
}
}
protected List<String> intersect(
final List<String> verified,
final List<String> current) {
final List<String> common =
new ArrayList<String>(current);
common.retainAll(verified);
return common;
}
protected void authAdm(
final HttpServletRequest req,
final Admin admin,
final List<String> verifiedEmails,
final List<String> verifiedOpenIds
) {
final Auth auth = new Auth();
auth.setAdmin(admin);
setupAuthCommon(req, auth, verifiedEmails, verifiedOpenIds);
}
protected void authStudent(
final HttpServletRequest req,
final Student student,
Group group, final List<String> verifiedEmails,
final List<String> verifiedOpenIds
) {
final Auth auth = new Auth();
auth.setStudent(student);
auth.setGroup(group);
setupAuthCommon(req, auth, verifiedEmails, verifiedOpenIds);
}
protected void setupAuthCommon(
final HttpServletRequest req,
final Auth auth,
final List<String> verifiedEmails,
final List<String> verifiedOpenIds
) {
auth.setSourceAddr(W.resolveRemoteAddress(req));
auth.setVerifiedOpenIds(verifiedOpenIds);
auth.setVerifiedEmails(verifiedEmails);
req.getSession(true).setAttribute(Auth.SESSION_KEY, auth);
req.getSession(true).setMaxInactiveInterval(
(int) (elwServerConfig.getSessionExpiryMillis() / 1000)
);
}
}