1a2011f25ac0bd4ce02ed0eb093ff169724f9047 angie Mon May 2 16:39:23 2016 -0700 Added trackDb setting "tableBrowser noGenome", which allows tables to appear in TB and DI menus, unlike "tableBrowser off", but does not allow genome-wide queries on the tables. The new "noGenome" setting is used for OMIM tracks -- OMIM gave the OK for non-genome-wide queries on their tables. See #4458#note-53 for details of UI & functional changes to the TB and DI for noGenome tracks. hg/lib/cartTrackDb.c handles access control based on trackDb tableBrowser settings (and the seldom-used tableAccessControl database tables that restrict tables to be viewed only by certain hosts). As before, 'tableBrowser off' means that the track is removed from the trackList so the TB & DI don't even know that it exists. 'tableBrowser noGenome' tracks are included in the trackList, but the tracks and any tables listed after 'noGenome' are hashed for later use by cartTrackDbIsNoGenome(). The hash used to contain permitted-host lists, but now contains structs that combine the permitted-host lists with a noGenome flag. hgIntegrator.c now includes a 'noGenome' flag in the JSONified groupedTrackDb, which the JS code uses to identify noGenome tracks. When executing a query, if the query region is genome but hgi_querySpec has noGenome related tables left over from past position-only queries, those related table settings are removed from the parsed JSON querySpec->config->relatedTables that is passed down into annoStreamDb. If one of the dataSources is noGenome but the region is genome (should be possible only by URL-tweaking), the region is forced to position. In hgTables, when region is genome, correlation and intersection track menu options for noGenome tracks are disabled. On the main page, JS code controls whether options are disabled depending on the current region. When listing related tables for filtering or selected fields output, if region is genome then the checkboxes for noGenome tables are disabled. If the cart has noGenome related table or intersection settings left over from past searches, they are ignored. If a noGenome query URL is tweaked to have hgta_region=genome, the TB errors out. hgIntegratorModel.js handles disabling of menu options and related table field settings for noGenome tracks when region is genome. refs #4458 diff --git src/hg/lib/cartTrackDb.c src/hg/lib/cartTrackDb.c index 9524676..984fce9 100644 --- src/hg/lib/cartTrackDb.c +++ src/hg/lib/cartTrackDb.c @@ -1,381 +1,425 @@ /* cartTrackDb - Combine custom tracks, hub tracks & hTrackDb to get unified groups/tracks/tables */ #include "common.h" #include "cartTrackDb.h" #include "cheapcgi.h" #include "customTrack.h" #include "hash.h" #include "hCommon.h" #include "hdb.h" #include "hgConfig.h" #include "hgMaf.h" #include "hubConnect.h" #include "joiner.h" #include "trackHub.h" #include "wikiTrack.h" /* Static globals */ static boolean useAC = FALSE; -static struct trackDb *forbiddenTrackList = NULL; +static struct slRef *accessControlTrackRefList = NULL; static struct trackDb *getFullTrackList(struct cart *cart, char *db, struct grp **pHubGroups) { struct trackDb *list = hTrackDb(db); struct customTrack *ctList, *ct; /* exclude any track with a 'tableBrowser off' setting */ struct trackDb *tdb, *nextTdb, *newList = NULL; for (tdb = list; tdb != NULL; tdb = nextTdb) { nextTdb = tdb->next; if (tdbIsDownloadsOnly(tdb) || tdb->table == NULL) { //freeMem(tdb); // should not free tdb's. // While hdb.c should and says it does cache the tdbList, it doesn't. // The most notable reason that the tdbs are not cached is this hgTables CGI !!! // It needs to be rewritten to make tdbRef structures for the lists it creates here! continue; } char *tbOff = trackDbSetting(tdb, "tableBrowser"); - if (useAC && tbOff != NULL && startsWithWord("off", tbOff)) - slAddHead(&forbiddenTrackList, tdb); + if (useAC && tbOff != NULL && + (startsWithWord("off", tbOff) || startsWithWord("noGenome", tbOff))) + { + slAddHead(&accessControlTrackRefList, slRefNew(tdb)); + if (! startsWithWord("off", tbOff)) + slAddHead(&newList, tdb); + } else slAddHead(&newList, tdb); } slReverse(&newList); list = newList; /* add wikiTrack if enabled */ if (wikiTrackEnabled(db, NULL)) slAddHead(&list, wikiTrackDb()); slSort(&list, trackDbCmp); // Add hub tracks at head of list struct trackDb *hubTdbList = hubCollectTracks(db, pHubGroups); list = slCat(list, hubTdbList); // Add custom tracks at head of list ctList = customTracksParseCart(db, cart, NULL, NULL); for (ct = ctList; ct != NULL; ct = ct->next) { slAddHead(&list, ct->tdb); } return list; } static struct grp *makeGroupList(char *db, struct trackDb *trackList, struct grp **pHubGrpList, boolean allTablesOk) /* Get list of groups that actually have something in them. */ { struct grp *groupsAll, *groupList = NULL, *group; struct hash *groupsInTrackList = newHash(0); struct hash *groupsInDatabase = newHash(0); struct trackDb *track; /* Stream through track list building up hash of active groups. */ for (track = trackList; track != NULL; track = track->next) { if (!hashLookup(groupsInTrackList,track->grp)) hashAdd(groupsInTrackList, track->grp, NULL); } /* Scan through group table, putting in ones where we have data. */ groupsAll = hLoadGrps(db); for (group = slPopHead(&groupsAll); group != NULL; group = slPopHead(&groupsAll)) { if (hashLookup(groupsInTrackList, group->name)) { slAddTail(&groupList, group); hashAdd(groupsInDatabase, group->name, group); } else grpFree(&group); } /* if we have custom tracks, we want to add the track hubs * after that group */ struct grp *addAfter = NULL; if ((groupList != NULL) && sameString(groupList->name, "user")) addAfter = groupList; /* Add in groups from hubs. */ for (group = slPopHead(pHubGrpList); group != NULL; group = slPopHead(pHubGrpList)) { // if the group isn't represented in any track, don't add it to list if (!hashLookup(groupsInTrackList,group->name)) continue; /* check to see if we're inserting hubs rather than * adding them to the front of the list */ struct grp *newGrp = grpDup(group); if (addAfter != NULL) { newGrp->next = addAfter->next; addAfter->next = newGrp; } else slAddHead(&groupList, newGrp); hashAdd(groupsInDatabase, newGrp->name, newGrp); } /* Do some error checking for tracks with group names that are * not in database. Just warn about them. */ if (!trackHubDatabase(db)) for (track = trackList; track != NULL; track = track->next) { if (!hashLookup(groupsInDatabase, track->grp)) warn("Track %s has group %s, which isn't in grp table", track->table, track->grp); } /* Create dummy group for all tracks. */ AllocVar(group); group->name = cloneString("allTracks"); group->label = cloneString("All Tracks"); slAddTail(&groupList, group); /* Create another dummy group for all tables. */ if (allTablesOk) { AllocVar(group); group->name = cloneString("allTables"); group->label = cloneString("All Tables"); slAddTail(&groupList, group); } hashFree(&groupsInTrackList); hashFree(&groupsInDatabase); return groupList; } void cartTrackDbInit(struct cart *cart, struct trackDb **retFullTrackList, struct grp **retFullGroupList, boolean useAccessControl) /* Get lists of all tracks and of groups that actually have tracks in them. * If useAccessControl, exclude tracks with 'tableBrowser off' nor tables listed * in the table tableAccessControl. */ { char *db = cartString(cart, "db"); useAC = useAccessControl; struct grp *hubGrpList = NULL; struct trackDb *fullTrackList = getFullTrackList(cart, db, &hubGrpList); boolean allTablesOk = hAllowAllTables() && !trackHubDatabase(db); struct grp *fullGroupList = makeGroupList(db, fullTrackList, &hubGrpList, allTablesOk); if (retFullTrackList != NULL) *retFullTrackList = fullTrackList; if (retFullGroupList != NULL) *retFullGroupList = fullGroupList; } static char *chopAtFirstDot(char *string) /* Terminate string at first '.' if found. Return string for convenience. */ { char *ptr = strchr(string, '.'); if (ptr != NULL) *ptr = '\0'; return string; } -static void hashAddSlName(struct hash *hash, char *key, char *val) -/* If key is already in hash, add an slName for val to the head of the list; - * otherwise just store key -> slName for val. */ +struct accessControl +/* Restricted permission settings for a table */ + { + struct slName *hostList; // List of hosts that are allowed to view this table + boolean isNoGenome; // True if it's OK for position range but not genome-wide query + }; + +void accessControlAddHost(struct accessControl *ac, char *host) +/* Alloc an slName for host (unless NULL or empty) and add it to ac->hostList. */ +{ +if (isNotEmpty(host)) + slAddHead(&ac->hostList, slNameNew(host)); +} + +struct accessControl *accessControlNew(char *host, boolean isNoGenome) +/* Alloc, init & return accessControl. */ { -struct slName *sln = slNameNew(val); -struct hashEl *hel = hashLookup(hash, key); +struct accessControl *ac; +AllocVar(ac); +accessControlAddHost(ac, host); +ac->isNoGenome = isNoGenome; +return ac; +} + +static void acHashAddOneTable(struct hash *acHash, char *table, char *host, boolean isNoGenome) +/* If table is already in acHash, update its accessControl fields; otherwise store a + * new accessControl for table. */ +{ +struct hashEl *hel = hashLookup(acHash, table); if (hel == NULL) - hashAdd(hash, key, sln); + { + struct accessControl *ac = accessControlNew(host, isNoGenome); + hashAdd(acHash, table, ac); + } else - slAddHead(&(hel->val), sln); + { + struct accessControl *ac = hel->val; + ac->isNoGenome = isNoGenome; + accessControlAddHost(ac, host); + } } -static struct hash *accessControlInit(struct sqlConnection *conn) +static struct hash *accessControlInit(char *db) /* Return a hash associating restricted table/track names in the given db/conn - * with virtual hosts, or NULL if there is no tableAccessControl table and no - * forbiddenTrackList (see getFullTrackList). */ + * with virtual hosts -- hash is empty if there is no tableAccessControl table and no + * accessControlTrackRefList (see getFullTrackList). */ { -struct hash *acHash = NULL; +struct hash *acHash = hashNew(0); +struct sqlConnection *conn = hAllocConn(db); if (sqlTableExists(conn, "tableAccessControl")) { struct sqlResult *sr = NULL; char **row = NULL; acHash = newHash(0); sr = sqlGetResult(conn, NOSQLINJ "select name,host from tableAccessControl"); while ((row = sqlNextRow(sr)) != NULL) - hashAddSlName(acHash, row[0], chopAtFirstDot(row[1])); + acHashAddOneTable(acHash, row[0], chopAtFirstDot(row[1]), FALSE); sqlFreeResult(&sr); } -if (forbiddenTrackList != NULL) - { - if (acHash == NULL) - acHash = newHash(0); - struct trackDb *tdb; - for (tdb = forbiddenTrackList; tdb != NULL; tdb = tdb->next) +struct slRef *tdbRef; +for (tdbRef = accessControlTrackRefList; tdbRef != NULL; tdbRef = tdbRef->next) { + struct trackDb *tdb = tdbRef->val; char *tbOff = cloneString(trackDbSetting(tdb, "tableBrowser")); if (isEmpty(tbOff)) - errAbort("bug: tdb for %s is in forbiddenTrackList without 'tableBrowser off' setting", + errAbort("accessControlInit bug: tdb for %s does not have tableBrowser setting", tdb->track); - hashAddSlName(acHash, tdb->table, "-"); - // skip "off" and look for additional table names: - nextWord(&tbOff); + // First word is "off" or "noGenome": + char *type = nextWord(&tbOff); + boolean isNoGenome = sameString(type, "noGenome"); + // Add track table to acHash: + acHashAddOneTable(acHash, tdb->table, NULL, isNoGenome); + // Remaining words are additional table names to add: char *tbl; while ((tbl = nextWord(&tbOff)) != NULL) - hashAddSlName(acHash, tbl, "-"); - } + acHashAddOneTable(acHash, tbl, NULL, isNoGenome); } +hFreeConn(&conn); return acHash; } -boolean cartTrackDbIsAccessDenied(char *db, char *table) -/* Return TRUE if useAccessControl=TRUE was passed to cartTrackDbInit and - * if access to table is denied (at least on this host) by 'tableBrowser off' - * or by the tableAccessControl table. */ +static struct hash *getCachedAcHash(char *db) +/* Returns a hash that maps table names to accessControl, creating it if necessary. */ { -static char *currentHost = NULL; static struct hash *dbToAcHash = NULL; - -if (!useAC) - return FALSE; - -struct slName *enabledHosts = NULL; -struct slName *sln = NULL; - if (dbToAcHash == NULL) dbToAcHash = hashNew(0); - struct hash *acHash = hashFindVal(dbToAcHash, db); if (acHash == NULL) { - struct sqlConnection *conn = hAllocConn(db); - acHash = accessControlInit(conn); - hFreeConn(&conn); + acHash = accessControlInit(db); hashAdd(dbToAcHash, db, acHash); } +return acHash; +} -if (acHash == NULL) - return FALSE; -enabledHosts = (struct slName *)hashFindVal(acHash, table); -if (enabledHosts == NULL) - return FALSE; +static char *getCachedCurrentHost() +/* Return the current host name chopped at the first '.' or NULL if not in the CGI environment. */ +{ +static char *currentHost = NULL; if (currentHost == NULL) { currentHost = cloneString(cgiServerName()); if (currentHost == NULL) - { - warn("accessControl: unable to determine current host"); - return FALSE; - } + return NULL; else chopAtFirstDot(currentHost); } -for (sln = enabledHosts; sln != NULL; sln = sln->next) +return currentHost; +} + +boolean cartTrackDbIsAccessDenied(char *db, char *table) +/* Return TRUE if useAccessControl=TRUE was passed to cartTrackDbInit and + * if access to table is denied (at least on this host) by 'tableBrowser off' + * or by the tableAccessControl table. */ { - if (sameString(currentHost, sln->name)) +if (!useAC) return FALSE; + +struct hash *acHash = getCachedAcHash(db); +struct accessControl *ac = hashFindVal(acHash, table); +if (ac == NULL) + return FALSE; +if (ac->isNoGenome && ac->hostList == NULL) + return FALSE; +char *currentHost = getCachedCurrentHost(); +if (currentHost == NULL) + warn("accessControl: unable to determine current host"); +return (! slNameInList(ac->hostList, currentHost)); } -return TRUE; + +boolean cartTrackDbIsNoGenome(char *db, char *table) +/* Return TRUE if range queries, but not genome-queries, are permitted for this table. */ +{ +struct hash *acHash = getCachedAcHash(db); +struct accessControl *ac = hashFindVal(acHash, table); +return (ac != NULL && ac->isNoGenome); } static void addTablesAccordingToTrackType(char *db, struct slName **pList, struct hash *uniqHash, struct trackDb *track) /* Parse out track->type and if necessary add some tables from it. */ { struct slName *name; char *trackDupe = cloneString(track->type); if (trackDupe != NULL && trackDupe[0] != 0) { char *s = trackDupe; char *type = nextWord(&s); if (sameString(type, "wigMaf")) { static char *wigMafAssociates[] = {"frames", "summary"}; int i; for (i=0; inext) { name = slNameNew(wig->table); slAddHead(pList, name); hashAdd(uniqHash, wig->table, NULL); } } if (track->subtracks) { struct slName *subList = NULL; struct slRef *tdbRefList = trackDbListGetRefsToDescendantLeaves(track->subtracks); slSort(&tdbRefList, trackDbRefCmp); struct slRef *tdbRef; for (tdbRef = tdbRefList; tdbRef != NULL; tdbRef = tdbRef->next) { struct trackDb *subTdb = tdbRef->val; name = slNameNew(subTdb->table); slAddTail(&subList, name); hashAdd(uniqHash, subTdb->table, NULL); } pList = slCat(pList, subList); } } freez(&trackDupe); } struct slName *cartTrackDbTablesForTrack(char *db, struct trackDb *track, boolean useJoiner) /* Return list of all tables associated with track. If useJoiner, the result can include * non-positional tables that are related to track by all.joiner. */ { static struct joiner *allJoiner = NULL; struct hash *uniqHash = newHash(8); struct slName *name, *nameList = NULL; char *trackTable = track->table; hashAdd(uniqHash, trackTable, NULL); if (useJoiner) { if (allJoiner == NULL) allJoiner = joinerRead("all.joiner"); struct joinerPair *jpList, *jp; jpList = joinerRelate(allJoiner, db, trackTable); for (jp = jpList; jp != NULL; jp = jp->next) { struct joinerDtf *dtf = jp->b; if (cartTrackDbIsAccessDenied(dtf->database, dtf->table)) continue; char buf[256]; char *s; if (sameString(dtf->database, db)) s = dtf->table; else { safef(buf, sizeof(buf), "%s.%s", dtf->database, dtf->table); s = buf; } if (!hashLookup(uniqHash, s)) { hashAdd(uniqHash, s, NULL); name = slNameNew(s); slAddHead(&nameList, name); } } slNameSort(&nameList); } /* suppress for parent tracks -- only the subtracks have tables */ if (track->subtracks == NULL) { name = slNameNew(trackTable); slAddHead(&nameList, name); } addTablesAccordingToTrackType(db, &nameList, uniqHash, track); hashFree(&uniqHash); return nameList; }