Fossil SCM
/attachlist now elides entries which are pending moderation unless they belong to the current user or a moderator.
Commit
686057e0e6b6d7ea4001f14d85c11a62893ca641680790cbf1efb63a490025e4
Parent
9e60ef975b21fd6…
2 files changed
+26
-10
+103
+26
-10
| --- src/attach.c | ||
| +++ src/attach.c | ||
| @@ -127,29 +127,45 @@ | ||
| 127 | 127 | } |
| 128 | 128 | blob_append_sql(&sql, " ORDER BY mtime DESC"); |
| 129 | 129 | db_prepare(&q, "%s", blob_sql_text(&sql)); |
| 130 | 130 | @ <ol> |
| 131 | 131 | while( db_step(&q)==SQLITE_ROW ){ |
| 132 | - const char *zDate = db_column_text(&q, 0); | |
| 133 | - const char *zSrc = db_column_text(&q, 1); | |
| 134 | - const char *zTarget = db_column_text(&q, 2); | |
| 135 | - const char *zFilename = db_column_text(&q, 3); | |
| 136 | - const char *zComment = db_column_text(&q, 4); | |
| 137 | - const char *zUser = db_column_text(&q, 5); | |
| 138 | - const char *zUuid = db_column_text(&q, 6); | |
| 132 | + const char *zDate; | |
| 133 | + const char *zSrc; | |
| 134 | + const char *zTarget; | |
| 135 | + const char *zFilename; | |
| 136 | + const char *zComment; | |
| 137 | + const char *zUser; | |
| 138 | + const char *zUuid; | |
| 139 | + const char *zDispUser; | |
| 139 | 140 | const int attachid = db_column_int(&q, 7); |
| 140 | - /* type 0 is a wiki page, 1 is a ticket, 2 is a tech note */ | |
| 141 | - const int type = attachment_target_type(zTarget); | |
| 142 | - const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; | |
| 141 | + int type; | |
| 143 | 142 | int i; |
| 144 | 143 | char *zUrlTail = 0; |
| 144 | + | |
| 145 | + if( moderation_pending(attachid) | |
| 146 | + && !moderation_user_could(attachid, 1, 0) ){ | |
| 147 | + /* Elide entries which are currently pending moderation unless | |
| 148 | + ** the user would be able to moderate the entry themselves. */ | |
| 149 | + continue; | |
| 150 | + } | |
| 151 | + | |
| 152 | + zDate = db_column_text(&q, 0); | |
| 153 | + zSrc = db_column_text(&q, 1); | |
| 154 | + zTarget = db_column_text(&q, 2); | |
| 155 | + zFilename = db_column_text(&q, 3); | |
| 156 | + zComment = db_column_text(&q, 4); | |
| 157 | + zUser = db_column_text(&q, 5); | |
| 158 | + zUuid = db_column_text(&q, 6); | |
| 159 | + zDispUser = zUser && zUser[0] ? zUser : "anonymous"; | |
| 145 | 160 | for(i=0; zFilename[i]; i++){ |
| 146 | 161 | if( zFilename[i]=='/' && zFilename[i+1]!=0 ){ |
| 147 | 162 | zFilename = &zFilename[i+1]; |
| 148 | 163 | i = -1; |
| 149 | 164 | } |
| 150 | 165 | } |
| 166 | + type = attachment_target_type(zTarget); | |
| 151 | 167 | switch( type ){ |
| 152 | 168 | case CFTYPE_TICKET: |
| 153 | 169 | zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename); |
| 154 | 170 | break; |
| 155 | 171 | case CFTYPE_EVENT: |
| 156 | 172 |
| --- src/attach.c | |
| +++ src/attach.c | |
| @@ -127,29 +127,45 @@ | |
| 127 | } |
| 128 | blob_append_sql(&sql, " ORDER BY mtime DESC"); |
| 129 | db_prepare(&q, "%s", blob_sql_text(&sql)); |
| 130 | @ <ol> |
| 131 | while( db_step(&q)==SQLITE_ROW ){ |
| 132 | const char *zDate = db_column_text(&q, 0); |
| 133 | const char *zSrc = db_column_text(&q, 1); |
| 134 | const char *zTarget = db_column_text(&q, 2); |
| 135 | const char *zFilename = db_column_text(&q, 3); |
| 136 | const char *zComment = db_column_text(&q, 4); |
| 137 | const char *zUser = db_column_text(&q, 5); |
| 138 | const char *zUuid = db_column_text(&q, 6); |
| 139 | const int attachid = db_column_int(&q, 7); |
| 140 | /* type 0 is a wiki page, 1 is a ticket, 2 is a tech note */ |
| 141 | const int type = attachment_target_type(zTarget); |
| 142 | const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; |
| 143 | int i; |
| 144 | char *zUrlTail = 0; |
| 145 | for(i=0; zFilename[i]; i++){ |
| 146 | if( zFilename[i]=='/' && zFilename[i+1]!=0 ){ |
| 147 | zFilename = &zFilename[i+1]; |
| 148 | i = -1; |
| 149 | } |
| 150 | } |
| 151 | switch( type ){ |
| 152 | case CFTYPE_TICKET: |
| 153 | zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename); |
| 154 | break; |
| 155 | case CFTYPE_EVENT: |
| 156 |
| --- src/attach.c | |
| +++ src/attach.c | |
| @@ -127,29 +127,45 @@ | |
| 127 | } |
| 128 | blob_append_sql(&sql, " ORDER BY mtime DESC"); |
| 129 | db_prepare(&q, "%s", blob_sql_text(&sql)); |
| 130 | @ <ol> |
| 131 | while( db_step(&q)==SQLITE_ROW ){ |
| 132 | const char *zDate; |
| 133 | const char *zSrc; |
| 134 | const char *zTarget; |
| 135 | const char *zFilename; |
| 136 | const char *zComment; |
| 137 | const char *zUser; |
| 138 | const char *zUuid; |
| 139 | const char *zDispUser; |
| 140 | const int attachid = db_column_int(&q, 7); |
| 141 | int type; |
| 142 | int i; |
| 143 | char *zUrlTail = 0; |
| 144 | |
| 145 | if( moderation_pending(attachid) |
| 146 | && !moderation_user_could(attachid, 1, 0) ){ |
| 147 | /* Elide entries which are currently pending moderation unless |
| 148 | ** the user would be able to moderate the entry themselves. */ |
| 149 | continue; |
| 150 | } |
| 151 | |
| 152 | zDate = db_column_text(&q, 0); |
| 153 | zSrc = db_column_text(&q, 1); |
| 154 | zTarget = db_column_text(&q, 2); |
| 155 | zFilename = db_column_text(&q, 3); |
| 156 | zComment = db_column_text(&q, 4); |
| 157 | zUser = db_column_text(&q, 5); |
| 158 | zUuid = db_column_text(&q, 6); |
| 159 | zDispUser = zUser && zUser[0] ? zUser : "anonymous"; |
| 160 | for(i=0; zFilename[i]; i++){ |
| 161 | if( zFilename[i]=='/' && zFilename[i+1]!=0 ){ |
| 162 | zFilename = &zFilename[i+1]; |
| 163 | i = -1; |
| 164 | } |
| 165 | } |
| 166 | type = attachment_target_type(zTarget); |
| 167 | switch( type ){ |
| 168 | case CFTYPE_TICKET: |
| 169 | zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename); |
| 170 | break; |
| 171 | case CFTYPE_EVENT: |
| 172 |
+103
| --- src/moderate.c | ||
| +++ src/moderate.c | ||
| @@ -225,5 +225,108 @@ | ||
| 225 | 225 | } |
| 226 | 226 | db_finalize(&q); |
| 227 | 227 | setup_incr_cfgcnt(); |
| 228 | 228 | db_end_transaction(0); |
| 229 | 229 | } |
| 230 | + | |
| 231 | +/* | |
| 232 | +** Returns true if the current user could ostensibly moderate the blob | |
| 233 | +** refered to by rid, irrespective of whether that object is currently | |
| 234 | +** pending moderation. If rid is not an event.objid value then this | |
| 235 | +** returns 0. | |
| 236 | +** | |
| 237 | +** If bMayDeny is true then a matching user is permitted to moderate a | |
| 238 | +** decline by not an approval. Pass 1 here if true should be returned | |
| 239 | +** if the current user matches the artifact. When passing false, it | |
| 240 | +** will only return true for users who have explicit moderation | |
| 241 | +** permissions. The purpose of this is to exclude pending-moderation | |
| 242 | +** from the current user in some contexts but not others. | |
| 243 | +** | |
| 244 | +** zWho is an optional user name to consider for ownership. If 0 it | |
| 245 | +** defaults to login_name(). | |
| 246 | +** | |
| 247 | +** The moderation rules applied here are: | |
| 248 | +** | |
| 249 | +** - Admins may always moderate. This is a fast path which bypasses | |
| 250 | +** artifact lookup. For non-admins, we look for a record in the | |
| 251 | +** event table. | |
| 252 | +** | |
| 253 | +** - Forum, Wiki, and Ticket moderators may always moderate a matching | |
| 254 | +** artifact. If bMayDeny is true then an artifact's owner, even if | |
| 255 | +** not a moderator, may moderate it. i.e. a non-moderator owner can | |
| 256 | +** reject their pending-moderation objects but they may not approve | |
| 257 | +** them. | |
| 258 | +** | |
| 259 | +** - Returns 0 for all other artifact types except that it will always | |
| 260 | +** return true for admins because that's | |
| 261 | +** | |
| 262 | + */ | |
| 263 | +int moderation_user_could(int rid, int bMayDeny, const char *zWho){ | |
| 264 | + static Stmt q; | |
| 265 | + int rc = 0; | |
| 266 | + if( g.perm.Admin ) return g.perm.Admin; | |
| 267 | + if( !q.pStmt ){ | |
| 268 | + db_static_prepare( | |
| 269 | + &q, | |
| 270 | + "SELECT coalesce(euser,user)=:user, type FROM event " | |
| 271 | + "WHERE objid=:rid" | |
| 272 | + ); | |
| 273 | + } | |
| 274 | + db_bind_int(&q, ":rid", rid); | |
| 275 | + db_bind_text(&q, ":user", zWho ? zWho : login_name()); | |
| 276 | + if( SQLITE_ROW==db_step(&q) ){ | |
| 277 | + const int bIsOwner = db_column_int(&q, 0); | |
| 278 | + const char *zType = db_column_text(&q, 1); | |
| 279 | + switch( zType ? zType[0] : 0 ){ | |
| 280 | + case 'f': rc = g.perm.ModForum || (bIsOwner && bMayDeny); break; | |
| 281 | + case 't': rc = g.perm.ModTkt || (bIsOwner && bMayDeny); break; | |
| 282 | + case 'w': rc = g.perm.ModWiki || (bIsOwner && bMayDeny); break; | |
| 283 | + /* case 'e': Technotes and their attachments are not subject | |
| 284 | + ** to moderation. */ | |
| 285 | + default: break; | |
| 286 | + } | |
| 287 | + } | |
| 288 | + db_reset(&q); | |
| 289 | + return rc; | |
| 290 | +} | |
| 291 | + | |
| 292 | + | |
| 293 | +/* | |
| 294 | +** COMMAND: test-user-could-moderate | |
| 295 | +** | |
| 296 | +** Usage: %d test-user-could-modeate ?-deny? user-name ...artifactNames | |
| 297 | +** | |
| 298 | +** Tests whether a given user would have the ability to moderate | |
| 299 | +** the given artifacts. The -deny flag indicates that the check should | |
| 300 | +** permit moderation if the artifact is owned by the same user. | |
| 301 | +*/ | |
| 302 | +void test_moderation_user_could_cmd(void){ | |
| 303 | + const char *zWho; | |
| 304 | + const int bMayDeny = find_option("deny",0,0) != 0; | |
| 305 | + char * zCap; | |
| 306 | + int i; | |
| 307 | + db_find_and_open_repository(0,0); | |
| 308 | + verify_all_options(); | |
| 309 | + if( g.argc<4 ){ | |
| 310 | + usage("user-name artifact-names..."); | |
| 311 | + } | |
| 312 | + zWho = g.zLogin = g.argv[2]; | |
| 313 | + zCap = db_text( | |
| 314 | + 0, "SELECT cap FROM user WHERE login=%Q", zWho | |
| 315 | + ); | |
| 316 | + if( !zCap ){ | |
| 317 | + fossil_fatal("Cannot determine capabilities of user %s", zWho); | |
| 318 | + } | |
| 319 | + login_set_capabilities(zCap, 0); | |
| 320 | + fossil_print("User: %s\nCaps: %s\n", zWho, zCap); | |
| 321 | + fossil_free(zCap); | |
| 322 | + for( i = 3; i < g.argc; ++i ){ | |
| 323 | + const char * zArg = g.argv[i]; | |
| 324 | + int rid = symbolic_name_to_rid(zArg, "*"); | |
| 325 | + int may; | |
| 326 | + if( rid<=0 ){ | |
| 327 | + fossil_fatal("Cannot resolve name: %s", zArg); | |
| 328 | + } | |
| 329 | + may = moderation_user_could(rid, bMayDeny, zWho); | |
| 330 | + fossil_print("%s\t\t=> %d\t=> %s\n", zArg, rid, may ? "yes" : "no"); | |
| 331 | + } | |
| 332 | +} | |
| 230 | 333 |
| --- src/moderate.c | |
| +++ src/moderate.c | |
| @@ -225,5 +225,108 @@ | |
| 225 | } |
| 226 | db_finalize(&q); |
| 227 | setup_incr_cfgcnt(); |
| 228 | db_end_transaction(0); |
| 229 | } |
| 230 |
| --- src/moderate.c | |
| +++ src/moderate.c | |
| @@ -225,5 +225,108 @@ | |
| 225 | } |
| 226 | db_finalize(&q); |
| 227 | setup_incr_cfgcnt(); |
| 228 | db_end_transaction(0); |
| 229 | } |
| 230 | |
| 231 | /* |
| 232 | ** Returns true if the current user could ostensibly moderate the blob |
| 233 | ** refered to by rid, irrespective of whether that object is currently |
| 234 | ** pending moderation. If rid is not an event.objid value then this |
| 235 | ** returns 0. |
| 236 | ** |
| 237 | ** If bMayDeny is true then a matching user is permitted to moderate a |
| 238 | ** decline by not an approval. Pass 1 here if true should be returned |
| 239 | ** if the current user matches the artifact. When passing false, it |
| 240 | ** will only return true for users who have explicit moderation |
| 241 | ** permissions. The purpose of this is to exclude pending-moderation |
| 242 | ** from the current user in some contexts but not others. |
| 243 | ** |
| 244 | ** zWho is an optional user name to consider for ownership. If 0 it |
| 245 | ** defaults to login_name(). |
| 246 | ** |
| 247 | ** The moderation rules applied here are: |
| 248 | ** |
| 249 | ** - Admins may always moderate. This is a fast path which bypasses |
| 250 | ** artifact lookup. For non-admins, we look for a record in the |
| 251 | ** event table. |
| 252 | ** |
| 253 | ** - Forum, Wiki, and Ticket moderators may always moderate a matching |
| 254 | ** artifact. If bMayDeny is true then an artifact's owner, even if |
| 255 | ** not a moderator, may moderate it. i.e. a non-moderator owner can |
| 256 | ** reject their pending-moderation objects but they may not approve |
| 257 | ** them. |
| 258 | ** |
| 259 | ** - Returns 0 for all other artifact types except that it will always |
| 260 | ** return true for admins because that's |
| 261 | ** |
| 262 | */ |
| 263 | int moderation_user_could(int rid, int bMayDeny, const char *zWho){ |
| 264 | static Stmt q; |
| 265 | int rc = 0; |
| 266 | if( g.perm.Admin ) return g.perm.Admin; |
| 267 | if( !q.pStmt ){ |
| 268 | db_static_prepare( |
| 269 | &q, |
| 270 | "SELECT coalesce(euser,user)=:user, type FROM event " |
| 271 | "WHERE objid=:rid" |
| 272 | ); |
| 273 | } |
| 274 | db_bind_int(&q, ":rid", rid); |
| 275 | db_bind_text(&q, ":user", zWho ? zWho : login_name()); |
| 276 | if( SQLITE_ROW==db_step(&q) ){ |
| 277 | const int bIsOwner = db_column_int(&q, 0); |
| 278 | const char *zType = db_column_text(&q, 1); |
| 279 | switch( zType ? zType[0] : 0 ){ |
| 280 | case 'f': rc = g.perm.ModForum || (bIsOwner && bMayDeny); break; |
| 281 | case 't': rc = g.perm.ModTkt || (bIsOwner && bMayDeny); break; |
| 282 | case 'w': rc = g.perm.ModWiki || (bIsOwner && bMayDeny); break; |
| 283 | /* case 'e': Technotes and their attachments are not subject |
| 284 | ** to moderation. */ |
| 285 | default: break; |
| 286 | } |
| 287 | } |
| 288 | db_reset(&q); |
| 289 | return rc; |
| 290 | } |
| 291 | |
| 292 | |
| 293 | /* |
| 294 | ** COMMAND: test-user-could-moderate |
| 295 | ** |
| 296 | ** Usage: %d test-user-could-modeate ?-deny? user-name ...artifactNames |
| 297 | ** |
| 298 | ** Tests whether a given user would have the ability to moderate |
| 299 | ** the given artifacts. The -deny flag indicates that the check should |
| 300 | ** permit moderation if the artifact is owned by the same user. |
| 301 | */ |
| 302 | void test_moderation_user_could_cmd(void){ |
| 303 | const char *zWho; |
| 304 | const int bMayDeny = find_option("deny",0,0) != 0; |
| 305 | char * zCap; |
| 306 | int i; |
| 307 | db_find_and_open_repository(0,0); |
| 308 | verify_all_options(); |
| 309 | if( g.argc<4 ){ |
| 310 | usage("user-name artifact-names..."); |
| 311 | } |
| 312 | zWho = g.zLogin = g.argv[2]; |
| 313 | zCap = db_text( |
| 314 | 0, "SELECT cap FROM user WHERE login=%Q", zWho |
| 315 | ); |
| 316 | if( !zCap ){ |
| 317 | fossil_fatal("Cannot determine capabilities of user %s", zWho); |
| 318 | } |
| 319 | login_set_capabilities(zCap, 0); |
| 320 | fossil_print("User: %s\nCaps: %s\n", zWho, zCap); |
| 321 | fossil_free(zCap); |
| 322 | for( i = 3; i < g.argc; ++i ){ |
| 323 | const char * zArg = g.argv[i]; |
| 324 | int rid = symbolic_name_to_rid(zArg, "*"); |
| 325 | int may; |
| 326 | if( rid<=0 ){ |
| 327 | fossil_fatal("Cannot resolve name: %s", zArg); |
| 328 | } |
| 329 | may = moderation_user_could(rid, bMayDeny, zWho); |
| 330 | fossil_print("%s\t\t=> %d\t=> %s\n", zArg, rid, may ? "yes" : "no"); |
| 331 | } |
| 332 | } |
| 333 |