Skip to content
Merged
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 @@ -14,6 +14,7 @@
import edu.suffolk.litlab.efsp.db.MessageSettingsDatabase;
import edu.suffolk.litlab.efsp.db.UserDatabase;
import edu.suffolk.litlab.efsp.docassemble.DocassembleToFilingInformationConverter;
import edu.suffolk.litlab.efsp.server.auth.AuthenticateRequestInterceptor;
import edu.suffolk.litlab.efsp.server.auth.SecurityHub;
import edu.suffolk.litlab.efsp.server.services.ApiUserSettingsService;
import edu.suffolk.litlab.efsp.server.services.AuthenticationService;
Expand All @@ -25,12 +26,12 @@
import edu.suffolk.litlab.efsp.server.setup.EfmRestCallbackInterface;
import edu.suffolk.litlab.efsp.server.setup.truefiling.TrueFilingModuleSetup;
import edu.suffolk.litlab.efsp.server.setup.tyler.TylerModuleSetup;
import edu.suffolk.litlab.efsp.server.utils.AuthenticateRequestInterceptor;
import edu.suffolk.litlab.efsp.server.utils.EnumExceptionMapper;
import edu.suffolk.litlab.efsp.server.utils.JsonExceptionMapper;
import edu.suffolk.litlab.efsp.server.utils.ObservabilityHeadersInterceptor;
import edu.suffolk.litlab.efsp.server.utils.ObservabilityResetInterceptor;
import edu.suffolk.litlab.efsp.server.utils.OrgMessageSender;
import edu.suffolk.litlab.efsp.server.utils.ProxyServerExceptionMapper;
import edu.suffolk.litlab.efsp.server.utils.SendMessage;
import edu.suffolk.litlab.efsp.server.utils.ServiceHelpers;
import edu.suffolk.litlab.efsp.server.utils.SoapExceptionMapper;
Expand Down Expand Up @@ -136,7 +137,7 @@ protected EfspServer(
Map.of(
"xml", MediaType.APPLICATION_XML,
"json", MediaType.APPLICATION_JSON));
sf.setProviders(providers(ldSupplier));
sf.setProviders(providers(ldSupplier, security));

sf.setAddress(ServiceHelpers.BASE_LOCAL_URL);
server = sf.create();
Expand Down Expand Up @@ -196,17 +197,18 @@ private static void setupDatabases(DataSource codeDs, DataSource userDs)
}
}

public static List<?> providers(Supplier<LoginDatabase> ldSupplier) {
public static List<?> providers(Supplier<LoginDatabase> ldSupplier, SecurityHub security) {
var objMapper = new ObjectMapper().registerModule(new Jdk8Module());
return List.of(
new JAXBElementProvider<Object>(),
new JacksonJsonProvider(objMapper), // TODO(brycew): JAXBJSon?
new SoapExceptionMapper(),
new JsonExceptionMapper(),
new EnumExceptionMapper(),
new ProxyServerExceptionMapper(),
new ObservabilityHeadersInterceptor(),
new ObservabilityResetInterceptor(),
new AuthenticateRequestInterceptor(ldSupplier));
new AuthenticateRequestInterceptor(ldSupplier, security));
}

public static void main(String[] args) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package edu.suffolk.litlab.efsp.server.utils;
package edu.suffolk.litlab.efsp.server.auth;

import edu.suffolk.litlab.efsp.Jurisdiction;
import edu.suffolk.litlab.efsp.db.LoginDatabase;
import edu.suffolk.litlab.efsp.db.model.AtRest;
import edu.suffolk.litlab.efsp.server.auth.TylerLogin;
import edu.suffolk.litlab.efsp.server.logging.MDCWrappers;
import edu.suffolk.litlab.efsp.tyler.TylerUserNamePassword;
import edu.suffolk.litlab.efsp.stdlib.NonEmptyString;
import edu.suffolk.litlab.efsp.utils.Hasher;
import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;
Expand All @@ -30,9 +29,12 @@ public class AuthenticateRequestInterceptor implements ContainerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(AuthenticateRequestInterceptor.class);
public static final String API_KEY_HEADER = "X-API-KEY";
private final Supplier<LoginDatabase> ldSupplier;
private final SecurityHub securityHub;

public AuthenticateRequestInterceptor(Supplier<LoginDatabase> ldSupplier) {
public AuthenticateRequestInterceptor(
Supplier<LoginDatabase> ldSupplier, SecurityHub securityHub) {
this.ldSupplier = ldSupplier;
this.securityHub = securityHub;
}

@Override
Expand All @@ -45,11 +47,19 @@ public void filter(ContainerRequestContext requestContext) throws IOException {
requestContext.abortWith(Response.status(401).build());
return;
}
var maybeJurisdiction =
Optional<Jurisdiction> maybeJurisdiction =
getJurisdiction(requestContext.getUriInfo().getAbsolutePath().getPath());
var maybeCreds = maybeJurisdiction.flatMap(j -> getCreds(requestContext, j));
var maybeCreds =
maybeJurisdiction.flatMap(
j -> getCreds(securityHub.getTokenHeaderValue(requestContext.getHeaders(), j)));
var p = maybeCreds.map(creds -> new TylerUserFromServer(creds));
var security = new EfspSecurityContext(p, atRest.get(), "");
Optional<NonEmptyString> userId =
maybeJurisdiction.flatMap(
j -> {
var headerVal = securityHub.getUserIdHeaderValue(requestContext.getHeaders(), j);
return NonEmptyString.create(headerVal);
});
var security = new EfspSecurityContext(p, userId, atRest.get(), "");
requestContext.setSecurityContext(security);
} catch (SQLException ex) {
log.error("SQL when trying to check API Key / login:", ex);
Expand Down Expand Up @@ -81,15 +91,12 @@ public static Optional<Jurisdiction> getJurisdiction(String path) {
* <p>Based on what the current path of the request is (it needs to be under "jurisdictions/..."
* to get the right login token).
*/
public static Optional<TylerUserNamePassword> getCreds(
ContainerRequestContext requestContext, Jurisdiction jurisdiction) {
String tylerToken =
requestContext.getHeaderString(TylerLogin.getHeaderKeyFromJurisdiction(jurisdiction));
var maybeCreds = TylerUserNamePassword.userCredsFromAuthorization(tylerToken);
public static Optional<UserCreds> getCreds(String token) {
var maybeCreds = UserNamePassword.userCredsFromAuthorization(token);
if (maybeCreds.isPresent()) {
MDC.put(MDCWrappers.USER_ID, Hasher.makeHash(tylerToken));
MDC.put(MDCWrappers.USER_ID, Hasher.makeHash(token));
} else {
if (tylerToken != null && !tylerToken.isBlank()) {
if (token != null && !token.isBlank()) {
MDC.put(MDCWrappers.USER_ID, "(invalid token)");
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package edu.suffolk.litlab.efsp.server.utils;
package edu.suffolk.litlab.efsp.server.auth;

import edu.suffolk.litlab.efsp.db.model.AtRest;
import edu.suffolk.litlab.efsp.stdlib.NonEmptyString;
import jakarta.annotation.Nullable;
import jakarta.ws.rs.core.SecurityContext;
import java.security.Principal;
import java.util.Optional;
Expand All @@ -9,12 +11,17 @@
public class EfspSecurityContext implements SecurityContext {

private final Optional<TylerUserFromServer> principal;
@Nullable private final Optional<NonEmptyString> userIdFromHeader;
private final AtRest serverInfo;
private final String role; // TODO: make this the Tyler role? idk how to do that every query

public EfspSecurityContext(
Optional<TylerUserFromServer> principal, AtRest serverInfo, String role) {
Optional<TylerUserFromServer> principal,
Optional<NonEmptyString> userIdFromHeader,
AtRest serverInfo,
String role) {
this.principal = principal;
this.userIdFromHeader = userIdFromHeader;
this.serverInfo = serverInfo;
this.role = role;
}
Expand All @@ -28,6 +35,18 @@ public Optional<TylerUserFromServer> getTylerUser() {
return principal;
}

public UserCreds getUserCreds() {
if (principal.isPresent()) {
return principal.get().creds();
} else {
return new NullUserCreds();
}
}

public Optional<NonEmptyString> getUserId() {
return userIdFromHeader;
}

public UUID getServerId() {
return serverInfo.serverId;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
package edu.suffolk.litlab.efsp.server.auth;

import com.fasterxml.jackson.databind.JsonNode;
import jakarta.ws.rs.core.MultivaluedMap;
import java.util.Optional;

public interface LoginInterface {

/** If LoginResult is present, login succeeded. */
Optional<LoginResult> login(JsonNode loginInfo);

String getLoginName();

String getHeaderKey();
/**
* Gets the required login token for all subsequent calls.
*
* @return might be null
*/
String getTokenHeaderValue(MultivaluedMap<String, String> headers);

/**
* Gets the user ID of the user initiating these calls.
*
* @return might be null
*/
String getUserIdHeaderValue(MultivaluedMap<String, String> headers);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package edu.suffolk.litlab.efsp.server.utils;
package edu.suffolk.litlab.efsp.server.auth;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package edu.suffolk.litlab.efsp.server.auth;

import java.util.List;
import org.apache.cxf.headers.Header;

public class NullUserCreds implements UserCreds {

public String getUserName() {
return "";
}

public List<Header> toHeaders() {
return List.of();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
import edu.suffolk.litlab.efsp.db.LoginDatabase;
import edu.suffolk.litlab.efsp.db.model.AtRest;
import edu.suffolk.litlab.efsp.db.model.NewTokens;
import jakarta.ws.rs.core.MultivaluedMap;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -30,8 +29,8 @@
public class SecurityHub {
private static final Logger log = LoggerFactory.getLogger(SecurityHub.class);

private final List<LoginInterface> tylerLoginObjs;
private final Map<String, Function<JsonNode, Optional<LoginResult>>> loginFunctions;
private final Map<String, LoginInterface> loginByKey;
private final Map<Jurisdiction, LoginInterface> loginByJurisdiction;
private final Supplier<LoginDatabase> ldSupplier;

/**
Expand All @@ -41,21 +40,33 @@ public class SecurityHub {
*/
public SecurityHub(Supplier<LoginDatabase> ldSupplier, List<Jurisdiction> jurisdictions) {
this.ldSupplier = ldSupplier;
if (jurisdictions.isEmpty()) {
this.tylerLoginObjs = List.of();
} else {
this.tylerLoginObjs =
jurisdictions.stream().map(j -> new TylerLogin(j)).collect(Collectors.toList());
this.loginByKey = new HashMap<>();
this.loginByJurisdiction = new HashMap<>();
for (var jurisdiction : jurisdictions) {
var login =
switch (jurisdiction.getVendor()) {
case Jurisdiction.Vendor.TYLER -> new TylerLogin(jurisdiction);
case Jurisdiction.Vendor.TRUE_FILING -> new TrueFilingLogin(jurisdiction);
};
this.loginByKey.put(login.getLoginName(), login);
this.loginByJurisdiction.put(jurisdiction, login);
}
}

this.loginFunctions = new HashMap<>();
this.loginFunctions.put(
"jeffnet",
info ->
Optional.of(new LoginResult(Map.of("JEFFNET-TOKEN", "deprecated"), Optional.empty())));
this.loginFunctions.putAll(
this.tylerLoginObjs.stream()
.collect(Collectors.toMap(lo -> lo.getLoginName(), lo -> (info) -> lo.login(info))));
public String getUserIdHeaderValue(
MultivaluedMap<String, String> headers, Jurisdiction jurisdiction) {
if (!this.loginByJurisdiction.containsKey(jurisdiction)) {
return null;
}
return this.loginByJurisdiction.get(jurisdiction).getUserIdHeaderValue(headers);
}

public String getTokenHeaderValue(
MultivaluedMap<String, String> headers, Jurisdiction jurisdiction) {
if (!this.loginByJurisdiction.containsKey(jurisdiction)) {
return null;
}
return this.loginByJurisdiction.get(jurisdiction).getTokenHeaderValue(headers);
}

/**
Expand Down Expand Up @@ -99,11 +110,15 @@ public Optional<NewTokens> login(String apiKey, JsonNode loginInfo) {
if (orgName.equalsIgnoreCase("api_key")) {
continue;
}
if (orgName.equalsIgnoreCase("jeffnet")) {
// Backwards compatible
newTokens.put("JEFFNET-TOKEN", "deprecated");
continue;
}
// TODO(brycew): feels hacky, but we don't want an additional column to the db for each new
// jurisdiction
if (!loginFunctions.containsKey(orgName)) {
log.error(
"There is no {} to login to: loginFunctions: {}", orgName, loginFunctions.keySet());
if (!loginByKey.containsKey(orgName)) {
log.error("There is no {} to login to: loginFunctions: {}", orgName, loginByKey.keySet());
return Optional.empty();
}

Expand All @@ -115,7 +130,7 @@ public Optional<NewTokens> login(String apiKey, JsonNode loginInfo) {
log.error("There is no {} to login to: enabled map: {}", permissionsName, atRest.enabled);
return Optional.empty();
}
Optional<LoginResult> maybeResult = loginFunctions.get(orgName).apply(loginInfo.get(orgName));
Optional<LoginResult> maybeResult = loginByKey.get(orgName).login(loginInfo.get(orgName));
if (maybeResult.isEmpty()) {
log.warn("Couldn't login to {}", orgName);
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package edu.suffolk.litlab.efsp.server.auth;

import com.fasterxml.jackson.databind.JsonNode;
import edu.suffolk.litlab.efsp.Jurisdiction;
import jakarta.ws.rs.core.MultivaluedMap;
import java.util.Map;
import java.util.Optional;

public class TrueFilingLogin implements LoginInterface {

private final Jurisdiction jurisdiction;

public TrueFilingLogin(Jurisdiction jurisdiction) {
this.jurisdiction = jurisdiction;
}

@Override
public Optional<LoginResult> login(JsonNode loginInfo) {
// TrueFiling doesn't have a user account system, so nothing to do!
return Optional.of(new LoginResult(Map.of(), Optional.empty()));
}

@Override
public String getLoginName() {
return "truefiling-" + jurisdiction.getName();
}

@Override
public String getTokenHeaderValue(MultivaluedMap<String, String> headers) {
return null;
}

@Override
public String getUserIdHeaderValue(MultivaluedMap<String, String> headers) {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import edu.suffolk.litlab.efsp.tyler.TylerClients;
import edu.suffolk.litlab.efsp.tyler.TylerUserClient;
import edu.suffolk.litlab.efsp.tyler.TylerUserFactory;
import jakarta.ws.rs.core.MultivaluedMap;
import java.util.Map;
import java.util.Optional;
import org.slf4j.Logger;
Expand Down Expand Up @@ -80,16 +81,25 @@ public Optional<LoginResult> login(JsonNode loginInfo) {
}

@Override
public String getHeaderKey() {
return getHeaderKeyFromJurisdiction(this.jurisdiction);
public String getTokenHeaderValue(MultivaluedMap<String, String> headers) {
return headers.getFirst(getHeaderKey());
}

@Override
public String getLoginName() {
return "tyler-" + this.jurisdiction.getName();
}

public static String getHeaderId(Jurisdiction jurisdiction) {
@Override
public String getUserIdHeaderValue(MultivaluedMap<String, String> headers) {
return headers.getFirst(getHeaderId(this.jurisdiction));
}

private String getHeaderKey() {
return getHeaderKeyFromJurisdiction(this.jurisdiction);
}

private static String getHeaderId(Jurisdiction jurisdiction) {
return "TYLER-ID-" + jurisdiction.getName().toUpperCase();
}
}
Loading
Loading