Skip to content

Commit

Permalink
Merge pull request #569 from ibi-group/add-auth-checks-to-feed-source…
Browse files Browse the repository at this point in the history
…-summary

Add auth checks to feed source summary
  • Loading branch information
br648 authored Oct 25, 2023
2 parents 5d426b7 + 3fc322f commit a7e5143
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import com.conveyal.datatools.manager.extensions.ExternalFeedResource;
import com.conveyal.datatools.manager.jobs.FetchSingleFeedJob;
import com.conveyal.datatools.manager.jobs.NotifyUsersForSubscriptionJob;
import com.conveyal.datatools.manager.models.DeploymentSummary;
import com.conveyal.datatools.manager.models.ExternalFeedSourceProperty;
import com.conveyal.datatools.manager.models.FeedRetrievalMethod;
import com.conveyal.datatools.manager.models.FeedSource;
Expand Down Expand Up @@ -87,7 +86,7 @@ private static Collection<FeedSource> getProjectFeedSources(Request req, Respons
boolean isAdmin = user.canAdministerProject(project);

Collection<FeedSource> projectFeedSources = project.retrieveProjectFeedSources();
for (FeedSource source: projectFeedSources) {
for (FeedSource source : projectFeedSources) {
String orgId = source.organizationId();
// If user can view or manage feed, add to list of feeds to return. NOTE: By default most users with access
// to a project should be able to view all feed sources. Custom privileges would need to be provided to
Expand All @@ -103,6 +102,31 @@ private static Collection<FeedSource> getProjectFeedSources(Request req, Respons
return feedSourcesToReturn;
}

private static Collection<FeedSourceSummary> getAllFeedSourceSummaries(Request req, Response res) {
Collection<FeedSourceSummary> feedSourcesToReturn = new ArrayList<>();
Auth0UserProfile user = req.attribute("user");
String projectId = req.queryParams("projectId");

Project project = Persistence.projects.getById(projectId);

if (project == null) {
logMessageAndHalt(req, 400, "Must provide valid projectId value.");
} else {
boolean isAdmin = user.canAdministerProject(project);
Collection<FeedSourceSummary> feedSourceSummaries = project.retrieveFeedSourceSummaries();
for (FeedSourceSummary feedSourceSummary : feedSourceSummaries) {
// If user can view or manage feed, add to list of feeds to return. NOTE: By default most users with access
// to a project should be able to view all feed sources. Custom privileges would need to be provided to
// override this behavior.
if (user.canManageOrViewFeed(project.organizationId, feedSourceSummary.projectId, feedSourceSummary.id)) {
// Remove labels user can't view, then add to list of feeds to return.
feedSourcesToReturn.add(cleanFeedSourceSummaryForNonAdmins(feedSourceSummary, isAdmin));
}
}
}
return feedSourcesToReturn;
}

/**
* HTTP endpoint to create a new feed source.
*/
Expand Down Expand Up @@ -297,7 +321,7 @@ private static FeedSource deleteFeedSource(Request req, Response res) {
/**
* Re-fetch this feed from the feed source URL.
*/
private static String fetch (Request req, Response res) {
private static String fetch(Request req, Response res) {
FeedSource s = requestFeedSourceById(req, Actions.MANAGE);
if (s.url == null) {
logMessageAndHalt(req, HttpStatus.BAD_REQUEST_400, "Cannot fetch feed source with null URL.");
Expand All @@ -315,7 +339,8 @@ private static String fetch (Request req, Response res) {

/**
* Helper function returns feed source if user has permission for specified action.
* @param req spark Request object from API request
*
* @param req spark Request object from API request
* @param action action type (either "view" or Permission.MANAGE)
* @return feedsource object for ID
*/
Expand Down Expand Up @@ -366,53 +391,81 @@ public static FeedSource checkFeedSourcePermissions(Request req, FeedSource feed
return cleanFeedSourceForNonAdmins(feedSource, isProjectAdmin);
}

/** Determines whether a change to a feed source is significant enough that it warrants sending a notification
/**
* Determines whether a change to a feed source is significant enough that it warrants sending a notification
*
* @param formerFeedSource A feed source object, without new changes
* @param updatedFeedSource A feed source object, with new changes
* @return A boolean value indicating if the updated feed source is changed enough to warrant sending a notification.
* @return A boolean value indicating if the updated feed source is changed enough to warrant sending a notification.
*/
private static boolean shouldNotifyUsersOnFeedUpdated(FeedSource formerFeedSource, FeedSource updatedFeedSource) {
return
// If only labels have changed, don't send out an email
formerFeedSource.equalsExceptLabels(updatedFeedSource);
// If only labels have changed, don't send out an email.
return formerFeedSource.equalsExceptLabels(updatedFeedSource);
}

/**
* Removes labels and notes from a feed that a user is not allowed to view. Returns cleaned feed source.
* @param feedSource The feed source to clean
* @param isAdmin Is the user an admin? Changes what is returned.
* @return A feed source containing only labels/notes the user is allowed to see
*
* @param feedSource The feed source to clean.
* @param isAdmin Is the user an admin? Changes what is returned.
* @return A feed source containing only labels/notes the user is allowed to see.
*/
protected static FeedSource cleanFeedSourceForNonAdmins(FeedSource feedSource, boolean isAdmin) {
// Admin can view all feed labels, but a non-admin should only see those with adminOnly=false
feedSource.labelIds = Persistence.labels
.getFiltered(PersistenceUtils.applyAdminFilter(in("_id", feedSource.labelIds), isAdmin)).stream()
feedSource.labelIds = cleanFeedSourceLabelIdsForNonAdmins(feedSource.labelIds, isAdmin);
feedSource.noteIds = cleanFeedSourceNotesForNonAdmins(feedSource.noteIds, isAdmin);
return feedSource;
}

/**
* Removes labels and notes from a feed that a user is not allowed to view. Returns cleaned feed source summary.
*
* @param feedSourceSummary The feed source to clean.
* @param isAdmin Is the user an admin? Changes what is returned.
* @return A feed source summary containing only labels/notes the user is allowed to see.
*/
protected static FeedSourceSummary cleanFeedSourceSummaryForNonAdmins(FeedSourceSummary feedSourceSummary, boolean isAdmin) {
// Admin can view all feed labels, but a non-admin should only see those with adminOnly=false
feedSourceSummary.labelIds = cleanFeedSourceLabelIdsForNonAdmins(feedSourceSummary.labelIds, isAdmin);
feedSourceSummary.noteIds = cleanFeedSourceNotesForNonAdmins(feedSourceSummary.noteIds, isAdmin);
return feedSourceSummary;
}

/**
* Removes labels from a feed that a user is not allowed to view. Returns cleaned notes.
*
* @param labelIds The labels to clean.
* @param isAdmin Is the user an admin? Changes what is returned.
* @return Labels the user is allowed to see.
*/
protected static List<String> cleanFeedSourceLabelIdsForNonAdmins(List<String> labelIds, boolean isAdmin) {
// Admin can view all feed labels, but a non-admin should only see those with adminOnly=false.
return Persistence.labels
.getFiltered(PersistenceUtils.applyAdminFilter(in("_id", labelIds), isAdmin))
.stream()
.map(label -> label.id)
.collect(Collectors.toList());
feedSource.noteIds = Persistence.notes
.getFiltered(PersistenceUtils.applyAdminFilter(in("_id", feedSource.noteIds), isAdmin)).stream()
.map(note -> note.id)
.collect(Collectors.toList());
return feedSource;
}

private static Collection<FeedSourceSummary> getAllFeedSourceSummaries(Request req, Response res) {
Auth0UserProfile userProfile = req.attribute("user");
String projectId = req.queryParams("projectId");
Project project = Persistence.projects.getById(projectId);
if (project == null) {
logMessageAndHalt(req, 400, "Must provide valid projectId value.");
}
if (!userProfile.canAdministerProject(project)) {
logMessageAndHalt(req, 401, "User not authorized to view project feed sources.");
}
return project.retrieveFeedSourceSummaries();
/**
* Removes notes from a feed that a user is not allowed to view. Returns cleaned notes.
*
* @param noteIds The notes to clean.
* @param isAdmin Is the user an admin? Changes what is returned.
* @return Notes the user is allowed to see.
*/
protected static List<String> cleanFeedSourceNotesForNonAdmins(List<String> noteIds, boolean isAdmin) {
// Admin can view all feed notes, but a non-admin should only see those with adminOnly=false.
return Persistence.notes
.getFiltered(PersistenceUtils.applyAdminFilter(in("_id", noteIds), isAdmin))
.stream()
.map(note -> note.id)
.collect(Collectors.toList());
}


// FIXME: use generic API controller and return JSON documents via BSON/Mongo
public static void register (String apiPrefix) {
public static void register(String apiPrefix) {
get(apiPrefix + "secure/feedsource/:id", FeedSourceController::getFeedSource, json::write);
get(apiPrefix + "secure/feedsource", FeedSourceController::getProjectFeedSources, json::write);
post(apiPrefix + "secure/feedsource", FeedSourceController::createFeedSource, json::write);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,16 @@ public class FeedSourceSummary {

public String url;

public List<String> noteIds = new ArrayList<>();

public String organizationId;

public FeedSourceSummary() {
}

public FeedSourceSummary(String projectId, Document feedSourceDocument) {
public FeedSourceSummary(String projectId, String organizationId, Document feedSourceDocument) {
this.projectId = projectId;
this.organizationId = organizationId;
this.id = feedSourceDocument.getString("_id");
this.name = feedSourceDocument.getString("name");
this.deployable = feedSourceDocument.getBoolean("deployable");
Expand All @@ -77,6 +82,10 @@ public FeedSourceSummary(String projectId, Document feedSourceDocument) {
if (documentLabelIds != null) {
this.labelIds = documentLabelIds;
}
List<String> documentNoteIds = feedSourceDocument.getList("noteIds", String.class);
if (documentNoteIds != null) {
this.noteIds = documentNoteIds;
}
// Convert to local date type for consistency.
this.lastUpdated = getLocalDateFromDate(feedSourceDocument.getDate("lastUpdated"));
this.url = feedSourceDocument.getString("url");
Expand Down Expand Up @@ -104,7 +113,7 @@ public void setFeedVersion(FeedVersionSummary feedVersionSummary, boolean isDepl
/**
* Get all feed source summaries matching the project id.
*/
public static List<FeedSourceSummary> getFeedSourceSummaries(String projectId) {
public static List<FeedSourceSummary> getFeedSourceSummaries(String projectId, String organizationId) {
/*
db.getCollection('FeedSource').aggregate([
{
Expand All @@ -121,7 +130,8 @@ public static List<FeedSourceSummary> getFeedSourceSummaries(String projectId) {
"isPublic": 1,
"lastUpdated": 1,
"labelIds": 1,
"url": 1
"url": 1,
"noteIds": 1
}
},
{
Expand All @@ -143,12 +153,13 @@ public static List<FeedSourceSummary> getFeedSourceSummaries(String projectId) {
"isPublic",
"lastUpdated",
"labelIds",
"url")
"url",
"noteIds")
)
),
sort(Sorts.ascending("name"))
);
return extractFeedSourceSummaries(projectId, stages);
return extractFeedSourceSummaries(projectId, organizationId, stages);
}

/**
Expand Down Expand Up @@ -423,10 +434,10 @@ public static Map<String, FeedVersionSummary> getFeedVersionsFromPinnedDeploymen
/**
* Produce a list of all feed source summaries for a project.
*/
private static List<FeedSourceSummary> extractFeedSourceSummaries(String projectId, List<Bson> stages) {
private static List<FeedSourceSummary> extractFeedSourceSummaries(String projectId, String organizationId, List<Bson> stages) {
List<FeedSourceSummary> feedSourceSummaries = new ArrayList<>();
for (Document feedSourceDocument : Persistence.getDocuments("FeedSource", stages)) {
feedSourceSummaries.add(new FeedSourceSummary(projectId, feedSourceDocument));
feedSourceSummaries.add(new FeedSourceSummary(projectId, organizationId, feedSourceDocument));
}
return feedSourceSummaries;
}
Expand Down Expand Up @@ -540,8 +551,11 @@ public static class LatestValidationResult {

public Integer errorCount;

/** Required for JSON de/serializing. **/
public LatestValidationResult() {}
/**
* Required for JSON de/serializing.
**/
public LatestValidationResult() {
}

LatestValidationResult(FeedVersionSummary feedVersionSummary) {
this.feedVersionId = feedVersionSummary.id;
Expand Down
11 changes: 9 additions & 2 deletions src/main/java/com/conveyal/datatools/manager/models/Label.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,25 @@ public String organizationId () {
public Auth0UserProfile user;

/**
* Create a new label
* Create a new label with auto-gen id.
*/
public Label (String name, String description, String color, boolean adminOnly, String projectId) {
super();
this.name = name;
this.description = description != null ? description : "";
this.color = color != null ? color : "#000000";
this.adminOnly = adminOnly;

this.projectId = projectId;
}

/**
* Create a new label with provided id.
*/
public Label (String id, String name, String description, String color, boolean adminOnly, String projectId) {
this(name, description, color, adminOnly, projectId);
this.id = id;
}

/**
* No-arg constructor to yield an uninitialized label, for dump/restore.
* Should not be used in general code.
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/com/conveyal/datatools/manager/models/Note.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ public class Note extends Model implements Serializable {
/** Whether the note should be visible to project admins only */
public boolean adminOnly;

/**
* Create a new note with provided id.
*/
public Note(String id, String body, boolean adminOnly) {
super();
this.id = id;
this.body = body;
this.adminOnly = adminOnly;
}

public Note() {
}

/**
* The types of object that can have notes recorded on them.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public Collection<DeploymentSummary> retrieveDeploymentSummaries() {
* Get all feed source summaries for this project.
*/
public Collection<FeedSourceSummary> retrieveFeedSourceSummaries() {
List<FeedSourceSummary> feedSourceSummaries = FeedSourceSummary.getFeedSourceSummaries(id);
List<FeedSourceSummary> feedSourceSummaries = FeedSourceSummary.getFeedSourceSummaries(id, organizationId);
Map<String, FeedVersionSummary> latestFeedVersionForFeedSources = FeedSourceSummary.getLatestFeedVersionForFeedSources(id);
Map<String, FeedVersionSummary> deployedFeedVersions = FeedSourceSummary.getFeedVersionsFromPinnedDeployment(id);
if (deployedFeedVersions.isEmpty()) {
Expand Down
Loading

0 comments on commit a7e5143

Please sign in to comment.