Skip to content

Commit

Permalink
Merge branch 'dev' into feature/OTP-1435-bus-notify-first-leg
Browse files Browse the repository at this point in the history
  • Loading branch information
br648 committed Nov 5, 2024
2 parents eeea569 + dd593cd commit c0d0672
Show file tree
Hide file tree
Showing 22 changed files with 720 additions and 37 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ 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. This page should support handling an error URL parameter. |
| 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
1 change: 1 addition & 0 deletions configurations/default/env.yml.tmp
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,4 @@ 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
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
@@ -1,17 +1,18 @@
package org.opentripplanner.middleware.controllers.api;

import io.github.manusant.ss.ApiEndpoint;
import com.twilio.rest.verify.v2.service.Verification;
import com.twilio.rest.verify.v2.service.VerificationCheck;
import io.github.manusant.ss.ApiEndpoint;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jetty.http.HttpStatus;
import org.opentripplanner.middleware.auth.Auth0Connection;
import org.opentripplanner.middleware.auth.RequestingUser;
import org.opentripplanner.middleware.models.MobilityProfile;
import org.opentripplanner.middleware.models.OtpUser;
import org.opentripplanner.middleware.persistence.Persistence;
import org.opentripplanner.middleware.tripmonitor.TrustedCompanion;
import org.opentripplanner.middleware.utils.JsonUtils;
import org.opentripplanner.middleware.utils.NotificationUtils;
import org.opentripplanner.middleware.utils.SwaggerUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spark.Request;
Expand All @@ -23,6 +24,10 @@
import java.util.regex.Pattern;

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

/**
Expand All @@ -33,11 +38,18 @@ public class OtpUserController extends AbstractUserController<OtpUser> {
private static final Logger LOG = LoggerFactory.getLogger(OtpUserController.class);

private static final String CODE_PARAM = "code";

private static final String PHONE_PARAM = "phoneNumber";

private static final String VERIFY_PATH = "verify_sms";

public static final String OTP_USER_PATH = "secure/user";

private static final String VERIFY_ROUTE_TEMPLATE = "/:%s/%s/:%s";
/** Regex to check E.164 phone number format per https://www.twilio.com/docs/glossary/what-e164 */

/**
* Regex to check E.164 phone number format per https://www.twilio.com/docs/glossary/what-e164
*/
private static final Pattern PHONE_E164_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$");

public OtpUserController(String apiPrefix) {
Expand All @@ -53,18 +65,25 @@ OtpUser preCreateHook(OtpUser user, Request req) {
Auth0Connection.ensureApiUserHasApiKey(req);
user.applicationId = requestingUser.apiUser.id;
}
if (Objects.nonNull(user.mobilityProfile)) {
user.mobilityProfile.updateMobilityMode();
}
preliminaryTasks(user);
return super.preCreateHook(user, req);
}

@Override
OtpUser preUpdateHook(OtpUser user, OtpUser preExistingUser, Request req) {
preliminaryTasks(user);
ensureRelatedUserIntegrity(user, preExistingUser);
return super.preUpdateHook(user, preExistingUser, req);
}

/**
* Tasks to be carried out before creating or updating a user.
*/
private void preliminaryTasks(OtpUser user) {
if (Objects.nonNull(user.mobilityProfile)) {
user.mobilityProfile.updateMobilityMode();
}
return super.preUpdateHook(user, preExistingUser, req);
manageAcceptDependentEmail(user);
}

@Override
Expand All @@ -73,6 +92,14 @@ protected void buildEndpoint(ApiEndpoint baseEndpoint) {

// Add the api key route BEFORE the regular CRUD methods
ApiEndpoint modifiedEndpoint = baseEndpoint
.get(path("/acceptdependent")
.withDescription("Accept a dependent request.")
.withResponses(SwaggerUtils.createStandardResponses(OtpUser.class))
.withPathParam().withName(ACCEPT_KEY).withRequired(true).withDescription("The accept dependent unique key.").and()
.withPathParam().withName(USER_LOCALE).withRequired(true).withDescription("The accepting user's locale.").and()
.withResponseType(OtpUser.class),
TrustedCompanion::acceptDependent
)
.get(path(ROOT_ROUTE + String.format(VERIFY_ROUTE_TEMPLATE, ID_PARAM, VERIFY_PATH, PHONE_PARAM))
.withDescription("Request an SMS verification to be sent to an OtpUser's phone number.")
.withPathParam().withName(ID_PARAM).withRequired(true).withDescription("The id of the OtpUser.").and()
Expand Down Expand Up @@ -183,4 +210,4 @@ public static boolean isPhoneNumberValidE164(String phoneNumber) {
Matcher m = PHONE_E164_PATTERN.matcher(phoneNumber);
return m.matches();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
* Message.properties.
*/
public enum Message {
ACCEPT_DEPENDENT_EMAIL_FOOTER,
ACCEPT_DEPENDENT_EMAIL_GREETING,
ACCEPT_DEPENDENT_EMAIL_LINK_TEXT,
ACCEPT_DEPENDENT_EMAIL_SUBJECT,
ACCEPT_DEPENDENT_EMAIL_MANAGE,
ACCEPT_DEPENDENT_ERROR,
LABEL_AND_CONTENT,
SMS_STOP_NOTIFICATIONS,
TRIP_EMAIL_SUBJECT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

Expand Down Expand Up @@ -173,6 +174,10 @@ public class MonitoredTrip extends Model {
*/
public boolean notifyAtLeadingInterval = true;

public RelatedUser primary;
public RelatedUser companion;
public List<RelatedUser> observers = new ArrayList<>();

/**
* The number of attempts made to obtain a trip's itinerary from OTP which matches this trip.
*/
Expand Down
37 changes: 35 additions & 2 deletions src/main/java/org/opentripplanner/middleware/models/OtpUser.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

import java.util.ArrayList;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.opentripplanner.middleware.tripmonitor.TrustedCompanion.removeDependent;

/**
* 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 @@ -43,7 +50,7 @@ public enum Notification {
* Notification preferences for this user
* (EMAIL and/or SMS and/or PUSH).
*/
public EnumSet<OtpUser.Notification> notificationChannel = EnumSet.noneOf(OtpUser.Notification.class);
public EnumSet<Notification> notificationChannel = EnumSet.noneOf(OtpUser.Notification.class);

/**
* Verified phone number for SMS notifications, in +15551234 format (E.164 format, includes country code, no spaces).
Expand Down Expand Up @@ -80,6 +87,15 @@ public enum Notification {
/** If this user was created by an {@link ApiUser}, this parameter will match the {@link ApiUser}'s id */
public String applicationId;

/** Companions and observers of this user. */
public List<RelatedUser> relatedUsers = new ArrayList<>();

/** Users that are dependent on this user. */
public List<String> dependents = new ArrayList<>();

/** This user's name */
public String name;

@Override
public boolean delete() {
return delete(true);
Expand Down Expand Up @@ -113,6 +129,23 @@ public boolean delete(boolean deleteAuth0User) {
}
}

// 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.email.equals(this.email)) {
relatedUser.status = RelatedUser.RelatedUserStatus.INVALID;
}
}
Persistence.otpUsers.replace(dependent.id, dependent);
}
}

// If a dependent, remove relationship with all related users.
for (RelatedUser relatedUser : relatedUsers) {
removeDependent(this, relatedUser);
}
return Persistence.otpUsers.removeById(this.id);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.opentripplanner.middleware.models;

/** A related user is a companion or observer requested by a dependent. */
public class RelatedUser {
public enum RelatedUserStatus {
PENDING, CONFIRMED, INVALID
}

public String email;
public RelatedUserStatus status = RelatedUserStatus.PENDING;
public String acceptKey;
public String nickname;

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

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

public RelatedUser(String email, RelatedUserStatus status, String nickname, String acceptKey) {
this (email, status, nickname);
this.acceptKey = acceptKey;
}
}

Loading

0 comments on commit c0d0672

Please sign in to comment.