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
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.DISPLAY;
import static edu.cornell.mannlib.vitro.webapp.utils.sparqlrunner.SparqlQueryRunner.createSelectQueryContext;
import static edu.cornell.mannlib.vitro.webapp.web.ContentType.TEXT_PLAIN;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.io.Writer;
import java.util.Collections;
import java.util.List;

Expand All @@ -29,6 +32,7 @@
import edu.cornell.library.scholars.webapp.controller.api.distribute.DataDistributor.NoSuchActionException;
import edu.cornell.library.scholars.webapp.controller.api.distribute.DataDistributor.NotAuthorizedException;
import edu.cornell.library.scholars.webapp.controller.api.distribute.DataDistributorContextImpl;
import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
import edu.cornell.mannlib.vitro.webapp.auth.objects.DataDistributorAccessObject;
import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper;
import edu.cornell.mannlib.vitro.webapp.controller.api.VitroApiServlet;
Expand All @@ -45,7 +49,6 @@
*/
@WebServlet(name = "DistributeDataApi", urlPatterns = { "/api/dataRequest/*" })
public class DistributeDataApiController extends VitroApiServlet {
private static final String NOT_AUTHORIZED_FOR_THIS_ACTION = "Not authorized for this action.";

private static final Log log = LogFactory.getLog(DistributeDataApiController.class);

Expand All @@ -71,7 +74,11 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
} catch (MissingParametersException e) {
do400BadRequest(e.getMessage(), resp);
} catch (NotAuthorizedException e) {
do403Forbidden(resp);
if (LoginStatusBean.getCurrentUser(req) == null) {
do401Unauthorized(resp, resp.getWriter());
} else {
do403Forbidden(resp, resp.getWriter());
}
} catch (Exception e) {
do500InternalServerError(e.getMessage(), e, resp);
}
Expand Down Expand Up @@ -134,11 +141,11 @@ private void runIt(HttpServletRequest req, HttpServletResponse resp, DataDistrib
resp.setCharacterEncoding("UTF-8");
instance.writeOutput(outputStream);
} catch (NotAuthorizedException e) {
log.debug("403 Forbidden");
resp.setContentType(TEXT_PLAIN.getMediaType());
resp.setStatus(403);
try (OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) {
writer.write(NOT_AUTHORIZED_FOR_THIS_ACTION);
if (LoginStatusBean.getCurrentUser(req) == null) {
do401Unauthorized(resp, new OutputStreamWriter(outputStream, UTF_8));
} else {
do403Forbidden(resp, new OutputStreamWriter(outputStream, UTF_8));
}
} catch (Exception e) {
log.error("Failed to execute the DataDistributor", e);
Expand All @@ -153,10 +160,17 @@ private void do400BadRequest(String message, HttpServletResponse resp) throws IO
resp.getWriter().println(message);
}

private void do403Forbidden(HttpServletResponse resp) throws IOException {
private void do403Forbidden(HttpServletResponse resp, Writer writer) throws IOException {
log.debug("403 Forbidden");
resp.setStatus(403);
resp.getWriter().println(NOT_AUTHORIZED_FOR_THIS_ACTION);
resp.setStatus(SC_FORBIDDEN);
writer.write("Not authorized for this action.");
}

private void do401Unauthorized(HttpServletResponse resp, Writer writer) throws IOException {
log.debug("401 Unauthorized");
resp.setHeader("WWW-Authenticate", "Basic realm=\"Secure Servlet Realm\"");
resp.setStatus(SC_UNAUTHORIZED);
writer.write("Unauthenticated.");
}

private void do500InternalServerError(String message, Exception e, HttpServletResponse resp) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

import static edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject.SOME_URI;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -34,7 +32,6 @@
import edu.cornell.mannlib.vitro.webapp.beans.Property;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator;
import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary;

/**
* A collection of static methods to help determine whether requested actions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_NOT_ACCEPTABLE;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;

import java.io.IOException;

Expand All @@ -15,7 +16,7 @@
import javax.servlet.http.HttpServletResponse;

import org.apache.jena.query.QueryParseException;

import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest;
import edu.cornell.mannlib.vitro.webapp.controller.api.sparqlquery.InvalidQueryTypeException;
Expand Down Expand Up @@ -69,7 +70,12 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp)
resp.setContentType(core.getMediaType());
core.executeAndFormat(resp.getOutputStream());
} catch (AuthException e) {
sendShortResponse(SC_FORBIDDEN, e.getMessage(), resp);
if (LoginStatusBean.getCurrentUser(req) == null) {
resp.setHeader("WWW-Authenticate", "Basic realm=\"Secure Servlet Realm\"");
sendShortResponse(SC_UNAUTHORIZED, e.getMessage(), resp);
} else {
sendShortResponse(SC_FORBIDDEN, e.getMessage(), resp);
}
} catch (BadParameterException e) {
sendShortResponse(SC_BAD_REQUEST, e.getMessage(), resp);
} catch (InvalidQueryTypeException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;

import java.io.IOException;
import java.io.InputStream;
Expand All @@ -31,7 +32,7 @@
import org.apache.jena.graph.Node;
import org.apache.jena.graph.NodeFactory;
import org.apache.jena.sparql.modify.UsingList;

import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils;
import edu.cornell.mannlib.vitro.webapp.auth.checks.UserOnThread;
import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission;
Expand Down Expand Up @@ -72,7 +73,12 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp)
executeUpdate(req, parsed);
do200response(resp);
} catch (AuthException e) {
do403response(resp, e);
if (LoginStatusBean.getCurrentUser(req) == null) {
resp.setHeader("WWW-Authenticate", "Basic realm=\"Secure Servlet Realm\"");
sendShortResponse(SC_UNAUTHORIZED, e.getMessage(), resp);
} else {
sendShortResponse(SC_FORBIDDEN, e.getMessage(), resp);
}
} catch (ParseException e) {
do400response(resp, e);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
Expand Down Expand Up @@ -42,35 +42,36 @@ public class VitroApiServlet extends HttpServlet {
*/
protected void confirmAuthorization(HttpServletRequest req,
AuthorizationRequest requiredActions) throws AuthException {
String email = req.getParameter("email");
String password = req.getParameter("password");

Authenticator auth = Authenticator.getInstance(req);
UserAccount account = auth.getAccountForInternalAuth(email);

if (auth.accountRequiresEditing(account)) {
log.debug("Account " + email + " requires editing.");
throw new AuthException("user account must include first and "
+ "last names and a valid email address.");
}

if (!auth.isCurrentPasswordArgon2(account, password)) {
log.debug("Invalid: '" + email + "'/'" + password + "'");
throw new AuthException("email/password combination is not valid");
}

if (!PolicyHelper.isAuthorizedForActions(req, email, password,
requiredActions)) {
log.debug("Not authorized: '" + email + "'");
UserAccount account = LoginStatusBean.getCurrentUser(req);
if (account == null) {
String email = req.getParameter("email");
String password = req.getParameter("password");

Authenticator auth = Authenticator.getInstance(req);
account = auth.getAccountForInternalAuth(email);

if (auth.accountRequiresEditing(account)) {
log.debug("Account " + email + " requires editing.");
throw new AuthException("user account must include first and "
+ "last names and a valid email address.");
}

if (!auth.isCurrentPasswordArgon2(account, password)) {
log.debug("Invalid: '" + email + "'/'" + password + "'");
throw new AuthException("email/password combination is not valid");
}
}
if (!PolicyHelper.isAuthorizedForActions(account, requiredActions)) {
log.debug("Not authorized: '" + account.getEmailAddress() + "'");
throw new AuthException("Account is not authorized");
}

if (account.isPasswordChangeRequired()) {
log.debug("Account " + email + " requires a new password.");
log.debug("Account " + account.getEmailAddress() + " requires a new password.");
throw new AuthException("user account requires a new password.");
}

log.debug("Authorized for '" + email + "'");
log.debug("Authorized for '" + account.getEmailAddress() + "'");
}

protected String parseAcceptHeader(HttpServletRequest req,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package edu.cornell.mannlib.vitro.webapp.filters;

import static edu.cornell.mannlib.vedit.beans.LoginStatusBean.AuthenticationSource.INTERNAL;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import edu.cornell.mannlib.vitro.webapp.auth.checks.UserOnThread;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator.LoginNotPermitted;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

@WebFilter(filterName = "Basic Authentication filter", urlPatterns = {"/*"})
public class BasicAuthFilter implements Filter {

private static final String PROPERTY_NAME = "authentication.basic";
private static final String ENABLED = "enabled";
private static final String UNAUTHORIZED_ACCESS = "Unauthorized access.";
private static final Log log = LogFactory.getLog(BasicAuthFilter.class);

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String authHeader = request.getHeader("Authorization");
boolean isDisabled = !isEnabled();
if (isDisabled || authHeader == null || !authHeader.startsWith("Basic ")) {
chain.doFilter(request, response);
return;
}
try {
String base64 = authHeader.substring(6).trim();
String credentials = new String(Base64.getDecoder().decode(base64), StandardCharsets.UTF_8);
String[] values = credentials.split(":", 2);
if ((values.length == 2)) {
String username = values[0];
String password = values[1];
Authenticator authenticator = Authenticator.getInstance(request);
UserAccount user = authenticator.getAccountForInternalAuth(username);
if (user != null && authenticator.isUserPermittedToLogin(user)
&& authenticator.isCurrentPasswordArgon2(user, password)) {
try (UserOnThread uot = new UserOnThread(user.getUri())) {
try {
authenticator.recordLoginAgainstUserAccount(user, INTERNAL);
} catch (LoginNotPermitted e) {
throw e;
}
}
chain.doFilter(request, response);
return;
} else {
response.sendError(SC_FORBIDDEN, UNAUTHORIZED_ACCESS);
return;
}
}
} catch (IllegalArgumentException | LoginNotPermitted e) {
log.error(e, e);
response.sendError(SC_BAD_REQUEST, UNAUTHORIZED_ACCESS);
}
}

private boolean isEnabled() {
return ENABLED.equalsIgnoreCase(ConfigurationProperties.getInstance().getProperty(PROPERTY_NAME));
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void destroy() {
}
}
3 changes: 3 additions & 0 deletions home/src/main/resources/config/example.runtime.properties
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ proxy.eligibleTypeList = http://www.w3.org/2002/07/owl#Thing
authentication.forgotPassword = disabled
authentication.forgotPassword.notify-admin = false

# Basic authentication
authentication.basic = enabled

# Captcha configuration. Available implementations are: nanocaptcha (text-based) and recaptchav2
# nanocaptcha is available in 2 difficulties (easy and hard)
# If captcha.implementation property is not provided, system will fall back to nanocaptcha implementation
Expand Down
4 changes: 4 additions & 0 deletions webapp/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@
<filter-name>Character Set Encoding Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Basic Authentication filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>Locale selection filter</filter-name>
<dispatcher>REQUEST</dispatcher>
Expand Down
Loading