7d56cd6635651dfd4c9926d062d92ad7b65a3e80 chmalee Wed Jun 3 14:52:52 2026 -0700 More myVariants changes: make the share to accept a comma-sep list of usernames, allow both the read/edit flag and the usernames setting to be editable after creation. When editing usernames, add a confirmation if the field is left blank that this will make the share viewable/editable by anyone with the link, refs #33808 diff --git src/hg/lib/myVariantsShare.c src/hg/lib/myVariantsShare.c index eedf1f82fc0..ab34ab6fb26 100644 --- src/hg/lib/myVariantsShare.c +++ src/hg/lib/myVariantsShare.c @@ -268,59 +268,135 @@ return share; } struct myVariantsShare *myVariantsGetSharesForOwner(struct sqlConnection *conn, char *ownerUser, char *db) /* Get all shares created by this user for the given assembly. */ { char query[512]; sqlSafef(query, sizeof(query), "SELECT * FROM myVariantsShares WHERE ownerUser='%s' AND db='%s'" " ORDER BY createdAt DESC", ownerUser, db); return myVariantsShareLoadByQuery(conn, query); } struct myVariantsShare *myVariantsGetSharesForUser(struct sqlConnection *conn, - char *targetUser, char *db) -/* Get all shares targeted at this user for the given assembly. */ + char *userName, char *db) +/* Get all shares targeted at this user for the given assembly. The targetUser + * column holds a normalized comma-separated list, so match with FIND_IN_SET. + * BINARY forces a case-sensitive match, matching myVariantsShareAllowsUser. */ { char query[512]; sqlSafef(query, sizeof(query), - "SELECT * FROM myVariantsShares WHERE targetUser='%s' AND db='%s'" - " ORDER BY createdAt DESC", targetUser, db); + "SELECT * FROM myVariantsShares WHERE FIND_IN_SET(BINARY '%s', targetUser) AND db='%s'" + " ORDER BY createdAt DESC", userName, db); return myVariantsShareLoadByQuery(conn, query); } boolean myVariantsRevokeShare(struct sqlConnection *conn, char *shareToken, char *ownerUser) /* Delete a share record. ownerUser must match the share's owner. * Returns TRUE if a row was deleted, FALSE if not found or not owner. */ { char query[512]; sqlSafef(query, sizeof(query), "SELECT count(*) FROM myVariantsShares WHERE shareToken='%s' AND ownerUser='%s'", shareToken, ownerUser); int count = sqlQuickNum(conn, query); if (count == 0) return FALSE; sqlSafef(query, sizeof(query), "DELETE FROM myVariantsShares WHERE shareToken='%s' AND ownerUser='%s'", shareToken, ownerUser); sqlUpdate(conn, query); return TRUE; } +static boolean shareOwnedBy(struct sqlConnection *conn, char *shareToken, char *ownerUser) +/* Return TRUE if a share with this token exists and is owned by ownerUser. */ +{ +char query[512]; +sqlSafef(query, sizeof(query), + "SELECT count(*) FROM myVariantsShares WHERE shareToken='%s' AND ownerUser='%s'", + shareToken, ownerUser); +return sqlQuickNum(conn, query) != 0; +} + +boolean myVariantsSetSharePermission(struct sqlConnection *conn, + char *shareToken, char *ownerUser, int permission) +/* Update a share's permission (0=read-only, 1=read-write). ownerUser must + * match the share's owner. Returns TRUE if a row was updated, FALSE if not + * found or not owner. */ +{ +if (permission != MYVAR_PERM_READONLY && permission != MYVAR_PERM_READWRITE) + return FALSE; +if (!shareOwnedBy(conn, shareToken, ownerUser)) + return FALSE; +char query[512]; +sqlSafef(query, sizeof(query), + "UPDATE myVariantsShares SET permission=%d WHERE shareToken='%s' AND ownerUser='%s'", + permission, shareToken, ownerUser); +sqlUpdate(conn, query); +return TRUE; +} + +boolean myVariantsSetShareTargets(struct sqlConnection *conn, + char *shareToken, char *ownerUser, char *targetUser) +/* Update a share's targetUser list (NULL for anyone with link). ownerUser + * must match the share's owner. Returns TRUE if a row was updated, FALSE if + * not found or not owner. */ +{ +if (!shareOwnedBy(conn, shareToken, ownerUser)) + return FALSE; +struct dyString *dy = sqlDyStringCreate("UPDATE myVariantsShares SET targetUser="); +if (isNotEmpty(targetUser)) + sqlDyStringPrintf(dy, "'%s'", targetUser); +else + sqlDyStringPrintf(dy, "NULL"); +sqlDyStringPrintf(dy, " WHERE shareToken='%s' AND ownerUser='%s'", shareToken, ownerUser); +sqlUpdate(conn, dy->string); +dyStringFree(&dy); +return TRUE; +} + +boolean myVariantsShareAllowsUser(struct myVariantsShare *share, char *userName) +/* Return TRUE if userName may access share. TRUE when targetUser is empty + * (anyone with link); otherwise TRUE only if userName is non-empty and + * appears in the comma-separated targetUser list. NULL-safe. */ +{ +if (share == NULL) + return FALSE; +if (isEmpty(share->targetUser)) + return TRUE; +if (isEmpty(userName)) + return FALSE; +boolean allowed = FALSE; +struct slName *names = slNameListFromComma(share->targetUser); +struct slName *name; +for (name = names; name != NULL; name = name->next) + { + trimSpaces(name->name); + if (sameString(name->name, userName)) + { + allowed = TRUE; + break; + } + } +slNameFreeList(&names); +return allowed; +} + char *myVariantsShareCartValue(struct myVariantsShare *share) /* Build JSON cart value string from a share record. * Caller must freeMem the result. */ { struct jsonWrite *jw = jsonWriteNew(); jsonWriteObjectStart(jw, NULL); jsonWriteString(jw, "owner", share->ownerUser); jsonWriteString(jw, "project", share->project); jsonWriteString(jw, "db", share->db); jsonWriteNumber(jw, "permission", share->permission); if (isNotEmpty(share->label)) jsonWriteString(jw, "label", share->label); jsonWriteObjectEnd(jw); char *result = cloneString(jw->dy->string); jsonWriteFree(&jw);