Skip to content

Commit

Permalink
refactor(Addressed PR feedback):
Browse files Browse the repository at this point in the history
  • Loading branch information
br648 committed Oct 25, 2024
1 parent 1d1f4fb commit 0db59a3
Show file tree
Hide file tree
Showing 15 changed files with 136 additions and 69 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ The special E2E client settings should be defined in `env.yml`:
| TRIP_INSTRUCTION_IMMEDIATE_RADIUS | integer | Optional | 2 | The radius in meters under which an immediate instruction is given. |
| TRIP_INSTRUCTION_UPCOMING_RADIUS | integer | Optional | 10 | The radius in meters under which an upcoming instruction is given. |
| TWILIO_ACCOUNT_SID | string | Optional | your-account-sid | Twilio settings available at: https://twilio.com/user/account |
| TRUSTED_COMPANION_CONFIRMATION_PAGE_URL | string | Optional | https://otp-server.example.com/trusted/confirmation | URL to the trusted companion confirmation page. |
| TRUSTED_COMPANION_ERROR_PAGE_URL | string | Optional | https://otp-server.example.com/trusted/error | URL to the trusted companion error page. |
| TWILIO_AUTH_TOKEN | string | Optional | your-auth-token | Twilio settings available at: https://twilio.com/user/account |
| US_RIDE_GWINNETT_BUS_OPERATOR_NOTIFIER_API_URL | string | Optional | http://host.example.com | US RideGwinnett bus notifier API. |
| US_RIDE_GWINNETT_BUS_OPERATOR_NOTIFIER_API_KEY | string | Optional | your-api-key | API key for the US RideGwinnett bus notifier API. |
Expand Down
2 changes: 2 additions & 0 deletions configurations/default/env.yml.tmp
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,5 @@ MAXIMUM_MONITORED_TRIP_ITINERARY_CHECKS: 3
# The location for an OTP plan query request.
PLAN_QUERY_RESOURCE_URI: https://plan.resource.com

TRUSTED_COMPANION_CONFIRMATION_PAGE_URL: https://otp-server.example.com/trusted/confirmation
TRUSTED_COMPANION_ERROR_PAGE_URL: https://otp-server.example.com/trusted/error
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import static org.opentripplanner.middleware.bugsnag.BugsnagWebhook.processWebHookDelivery;
import static org.opentripplanner.middleware.controllers.api.ApiUserController.API_USER_PATH;
import static org.opentripplanner.middleware.controllers.api.ApiUserController.AUTHENTICATE_PATH;
import static org.opentripplanner.middleware.tripmonitor.TrustedCompanion.ACCEPT_DEPENDENT_PATH;
import static org.opentripplanner.middleware.utils.JsonUtils.logMessageAndHalt;

/**
Expand Down Expand Up @@ -172,7 +173,11 @@ private static void initializeHttpEndpoints() throws IOException, InterruptedExc
// Security checks for admin and /secure/ endpoints. Excluding /authenticate so that API users can obtain a
// bearer token to authenticate against all other /secure/ endpoints.
spark.before(API_PREFIX + "/secure/*", ((request, response) -> {
if (!request.requestMethod().equals("OPTIONS") && !request.pathInfo().endsWith(API_USER_PATH + AUTHENTICATE_PATH)) {
if (
!request.requestMethod().equals("OPTIONS") &&
!request.pathInfo().endsWith(API_USER_PATH + AUTHENTICATE_PATH) &&
!request.pathInfo().endsWith(ACCEPT_DEPENDENT_PATH)
) {
Auth0Connection.checkUser(request);
}
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.regex.Pattern;

import static io.github.manusant.ss.descriptor.MethodDescriptor.path;
import static org.opentripplanner.middleware.tripmonitor.TrustedCompanion.REQUESTING_USER_ID_PARAM;
import static org.opentripplanner.middleware.tripmonitor.TrustedCompanion.manageAcceptDependentEmail;
import static org.opentripplanner.middleware.utils.JsonUtils.logMessageAndHalt;

Expand Down Expand Up @@ -87,11 +88,9 @@ protected void buildEndpoint(ApiEndpoint baseEndpoint) {
.get(path("/acceptdependent")
.withDescription("Accept a dependent request.")
.withResponses(SwaggerUtils.createStandardResponses(OtpUser.class))
.withPathParam()
.withName(USER_ID_PARAM)
.withRequired(true)
.withDescription("The dependent user id.")
.and(),
.withPathParam().withName(USER_ID_PARAM).withRequired(true).withDescription("The dependent user id.").and()
.withPathParam().withName(REQUESTING_USER_ID_PARAM).withRequired(true).withDescription("The requesting user id.").and()
.withResponseType(OtpUser.class),
TrustedCompanion::acceptDependent
)
.get(path(ROOT_ROUTE + String.format(VERIFY_ROUTE_TEMPLATE, ID_PARAM, VERIFY_PATH, PHONE_PARAM))
Expand Down
18 changes: 10 additions & 8 deletions src/main/java/org/opentripplanner/middleware/models/OtpUser.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.mongodb.client.model.Filters.eq;

/**
* This represents a user of an OpenTripPlanner instance (typically of the standard OTP UI/otp-react-redux).
* otp-middleware stores these users and associated information (e.g., home/work locations and other favorites). Users
Expand Down Expand Up @@ -124,25 +126,25 @@ public boolean delete(boolean deleteAuth0User) {
}
}

// If a guardian, invalidate relationship with all dependents.
for (String userId: dependents) {
// If a related user, invalidate relationship with all dependents.
for (String userId : dependents) {
OtpUser dependent = Persistence.otpUsers.getById(userId);
if (dependent != null) {
for (RelatedUser relatedUser : dependent.relatedUsers) {
if (relatedUser.userId.equals(this.id)) {
if (relatedUser.email.equals(this.email)) {
relatedUser.status = RelatedUser.RelatedUserStatus.INVALID;
}
}
Persistence.otpUsers.replace(dependent.id, dependent);
}
}

// If a dependent, remove relationship with all guardians.
// If a dependent, remove relationship with all related users.
for (RelatedUser relatedUser : relatedUsers) {
OtpUser guardian = Persistence.otpUsers.getById(relatedUser.userId);
if (guardian != null) {
guardian.dependents.remove(this.id);
Persistence.otpUsers.replace(guardian.id, guardian);
OtpUser user = Persistence.otpUsers.getOneFiltered(eq("email", relatedUser.email));
if (user != null) {
user.dependents.remove(this.id);
Persistence.otpUsers.replace(user.id, user);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ public enum RelatedUserStatus {
PENDING, CONFIRMED, INVALID
}

public String userId;
public String email;
public RelatedUserStatus status;
public RelatedUserStatus status = RelatedUserStatus.PENDING;
public boolean acceptDependentEmailSent;
public String nickName;

public RelatedUser() {
// Required for JSON deserialization.
}

public RelatedUser(String userId, String email, RelatedUserStatus status) {
this.userId = userId;
public RelatedUser(String email, RelatedUserStatus status, String nickName) {
this.email = email;
this.status = status;
this.nickName = nickName;
}
}

Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package org.opentripplanner.middleware.tripmonitor;

import org.eclipse.jetty.http.HttpStatus;
import org.opentripplanner.middleware.auth.Auth0Connection;
import org.opentripplanner.middleware.auth.RequestingUser;
import org.opentripplanner.middleware.OtpMiddlewareMain;
import org.opentripplanner.middleware.i18n.Message;
import org.opentripplanner.middleware.models.OtpUser;
import org.opentripplanner.middleware.models.RelatedUser;
Expand All @@ -18,6 +17,7 @@
import java.util.Locale;
import java.util.Map;

import static com.mongodb.client.model.Filters.eq;
import static org.opentripplanner.middleware.controllers.api.ApiController.USER_ID_PARAM;
import static org.opentripplanner.middleware.tripmonitor.jobs.CheckMonitoredTrip.SETTINGS_PATH;
import static org.opentripplanner.middleware.utils.I18nUtils.label;
Expand All @@ -29,54 +29,74 @@ private TrustedCompanion() {
throw new IllegalStateException("Utility class");
}

private static final String AWS_API_SERVER = ConfigUtils.getConfigPropertyAsText("AWS_API_SERVER");
private static final String AWS_API_STAGE = ConfigUtils.getConfigPropertyAsText("AWS_API_STAGE");
private static final String OTP_UI_URL = ConfigUtils.getConfigPropertyAsText("OTP_UI_URL");
private static final String TRUSTED_COMPANION_CONFIRMATION_PAGE_URL = ConfigUtils.getConfigPropertyAsText("TRUSTED_COMPANION_CONFIRMATION_PAGE_URL");
private static final String TRUSTED_COMPANION_ERROR_PAGE_URL = ConfigUtils.getConfigPropertyAsText("TRUSTED_COMPANION_ERROR_PAGE_URL");
public static final String REQUESTING_USER_ID_PARAM = "requestingUserId";

/** Note: This path is excluded from security checks, see {@link OtpMiddlewareMain#initializeHttpEndpoints()}. */
public static final String ACCEPT_DEPENDENT_PATH = "api/secure/user/acceptdependent";

/**
* Accept a request from another user to be their dependent. This will include both companions and observers.
* Accept a request from another user to be their dependent. This will include both companions and observers. If
* successful redirect the user to the confirmation page, else redirect to the error page with related error.
*/
public static OtpUser acceptDependent(Request request, Response response) {
RequestingUser requestingUser = Auth0Connection.getUserFromRequest(request);
OtpUser relatedUser = requestingUser.otpUser;
if (relatedUser == null) {
logMessageAndHalt(request, HttpStatus.BAD_REQUEST_400, "Otp user unknown.");
return null;
boolean accepted = false;
String error = "";
OtpUser relatedUser = getUserFromRequest(request, REQUESTING_USER_ID_PARAM);
OtpUser dependentUser = getUserFromRequest(request, USER_ID_PARAM);
if (relatedUser != null && dependentUser != null) {
boolean isRelated = dependentUser.relatedUsers
.stream()
.filter(related -> related.email.equals(relatedUser.email))
// Update related user status. This assumes a related user with "pending" status was previously added.
.peek(related -> related.status = RelatedUser.RelatedUserStatus.CONFIRMED)
.findFirst()
.isPresent();

if (isRelated) {
// Maintain a list of dependents.
relatedUser.dependents.add(dependentUser.id);
Persistence.otpUsers.replace(relatedUser.id, relatedUser);
// Update list of related users.
Persistence.otpUsers.replace(dependentUser.id, dependentUser);
accepted = true;
} else {
error = "Dependent did not request you as a trusted companion!";
}
} else {
error = "Required users do not exist!";
}

String dependentUserId = HttpUtils.getQueryParamFromRequest(request, USER_ID_PARAM, false);
if (dependentUserId.isEmpty()) {
logMessageAndHalt(request, HttpStatus.BAD_REQUEST_400, "Dependent user id not provided.");
if (accepted) {
// Redirect to confirmation page and provide dependent user information.
response.redirect(TRUSTED_COMPANION_CONFIRMATION_PAGE_URL);
return dependentUser;
} else {
response.redirect(String.format("%s?error=%s", TRUSTED_COMPANION_ERROR_PAGE_URL, error));
return null;
}
}

OtpUser dependentUser = Persistence.otpUsers.getById(dependentUserId);
if (dependentUser == null) {
logMessageAndHalt(request, HttpStatus.BAD_REQUEST_400, "Dependent user unknown!");
/**
* Extract the user id from the request parameters and in-turn retrieve the matching user.
*/
private static OtpUser getUserFromRequest(Request request, String parameterName) {
String userId = HttpUtils.getQueryParamFromRequest(request, parameterName, false);
if (userId.isEmpty()) {
logMessageAndHalt(request, HttpStatus.BAD_REQUEST_400, "User id not provided.");
return null;
}

boolean isRelated = dependentUser.relatedUsers
.stream()
.filter(related -> related.userId.equals(relatedUser.id))
// Update related user status. This assumes a related user with "pending" status was previously added.
.peek(related -> related.status = RelatedUser.RelatedUserStatus.CONFIRMED)
.findFirst()
.isPresent();

if (isRelated) {
// Maintain a list of dependents.
relatedUser.dependents.add(dependentUserId);
Persistence.otpUsers.replace(relatedUser.id, relatedUser);
// Update list of related users.
Persistence.otpUsers.replace(dependentUser.id, dependentUser);
} else {
logMessageAndHalt(request, HttpStatus.BAD_REQUEST_400, "Dependent did not request user to be related!");
OtpUser user = Persistence.otpUsers.getById(userId);
if (user == null) {
logMessageAndHalt(request, HttpStatus.BAD_REQUEST_400, "Otp user unknown.");
return null;
}

// TODO: Not sure what is required in the response. For now, returning the updated related user.
return relatedUser;
return user;
}

public static void manageAcceptDependentEmail(OtpUser dependentUser) {
Expand All @@ -98,7 +118,7 @@ public static void manageAcceptDependentEmail(OtpUser dependentUser, boolean isT
.stream()
.filter(relatedUser -> !relatedUser.acceptDependentEmailSent)
.forEach(relatedUser -> {
OtpUser userToReceiveEmail = Persistence.otpUsers.getById(relatedUser.userId);
OtpUser userToReceiveEmail = Persistence.otpUsers.getOneFiltered(eq("email", relatedUser.email));
if (userToReceiveEmail != null && (isTest || sendAcceptDependentEmail(dependentUser, userToReceiveEmail))) {
relatedUser.acceptDependentEmailSent = true;
}
Expand All @@ -125,7 +145,7 @@ private static boolean sendAcceptDependentEmail(OtpUser dependentUser, OtpUser r
"acceptDependentUrl", getAcceptDependentUrl(dependentUser),
"emailFooter", Message.ACCEPT_DEPENDENT_EMAIL_FOOTER.get(locale),
// TODO: The user's email address isn't very personal, but that is all I have to work with! Suggetions?
"emailGreeting", String.format("%s%s", dependentUser.email, Message.ACCEPT_DEPENDENT_EMAIL_GREETING.get(locale)),
"emailGreeting", String.format("%s %s", dependentUser.email, Message.ACCEPT_DEPENDENT_EMAIL_GREETING.get(locale)),
// TODO: This is required in the `OtpUserContainer.ftl` template. Not sure what to provide. Suggestions?
"manageLinkUrl", String.format("%s%s", OTP_UI_URL, SETTINGS_PATH),
"manageLinkText", Message.ACCEPT_DEPENDENT_EMAIL_MANAGE.get(locale)
Expand All @@ -142,7 +162,6 @@ private static boolean sendAcceptDependentEmail(OtpUser dependentUser, OtpUser r
}

private static String getAcceptDependentUrl(OtpUser dependentUser) {
// TODO: Is OTP_UI_URL the correct base URL to user here? I'm not sure.
return String.format("%s%s?userId=%s", OTP_UI_URL, ACCEPT_DEPENDENT_PATH, dependentUser.id);
return String.format("%s/%s/%s?userId=%s", AWS_API_SERVER, AWS_API_STAGE, ACCEPT_DEPENDENT_PATH, dependentUser.id);
}
}
2 changes: 1 addition & 1 deletion src/main/resources/Message.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ACCEPT_DEPENDENT_EMAIL_FOOTER = You are receiving this email because you have been selected to be a trusted companion.
ACCEPT_DEPENDENT_EMAIL_GREETING = %s would like you to be a trusted companion.
ACCEPT_DEPENDENT_EMAIL_GREETING = would like you to be a trusted companion.
ACCEPT_DEPENDENT_EMAIL_LINK_TEXT = Accept trusted companion
ACCEPT_DEPENDENT_EMAIL_SUBJECT = Trusted companion request
ACCEPT_DEPENDENT_EMAIL_MANAGE = Manage settings
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/Message_fr.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ACCEPT_DEPENDENT_EMAIL_FOOTER = TODO
ACCEPT_DEPENDENT_EMAIL_GREETING = %s TODO.
ACCEPT_DEPENDENT_EMAIL_GREETING = TODO
ACCEPT_DEPENDENT_EMAIL_LINK_TEXT = TODO
ACCEPT_DEPENDENT_EMAIL_SUBJECT = TODO
ACCEPT_DEPENDENT_EMAIL_MANAGE = TODO
Expand Down
10 changes: 10 additions & 0 deletions src/main/resources/env.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,16 @@
"examples": ["your-account-sid"],
"description": "Twilio settings available at: https://twilio.com/user/account"
},
"TRUSTED_COMPANION_CONFIRMATION_PAGE_URL": {
"type": "string",
"examples": ["https://otp-server.example.com/trusted/confirmation"],
"description": "URL to the trusted companion confirmation page."
},
"TRUSTED_COMPANION_ERROR_PAGE_URL": {
"type": "string",
"examples": ["https://otp-server.example.com/trusted/error"],
"description": "URL to the trusted companion error page."
},
"TWILIO_AUTH_TOKEN": {
"type": "string",
"examples": ["your-auth-token"],
Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/latest-spark-swagger-output.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2877,8 +2877,6 @@ definitions:
RelatedUser:
type: "object"
properties:
userId:
type: "string"
email:
type: "string"
status:
Expand All @@ -2889,6 +2887,8 @@ definitions:
- "INVALID"
acceptDependentEmailSent:
type: "boolean"
nickName:
type: "string"
FareComponent:
type: "object"
properties:
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/templates/AcceptDependentText.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
-->
${emailGreeting}

${tripLinkLabelAndUrl}
${acceptDependentLinkLabelAndUrl}
Loading

0 comments on commit 0db59a3

Please sign in to comment.