feat(bot-mitigations): only logged in table sorting

This commit is contained in:
Gregor Kleen 2021-02-21 13:39:47 +01:00
parent 8b532a2b3b
commit fb6ae089c6
11 changed files with 113 additions and 23 deletions

View File

@ -281,3 +281,6 @@ file-source-prewarm:
inhibit: 3600 # 60m inhibit: 3600 # 60m
steps: 20 steps: 20
max-speedup: 3 max-speedup: 3
bot-mitigations:
- only-logged-in-table-sorting

View File

@ -993,6 +993,11 @@ th, td
padding-right: 24px padding-right: 24px
cursor: pointer cursor: pointer
.table__th.presorted,
.table__th.presorted
position: relative
padding-right: 24px
.table__th.sortable::after, .table__th.sortable::after,
.table__th.sortable::before .table__th.sortable::before
content: '' content: ''
@ -1022,6 +1027,28 @@ th, td
.table__th.sorted-desc::after .table__th.sorted-desc::after
border-bottom-color: white !important border-bottom-color: white !important
.table__th.presorted::before,
.table__th.presorted::after
content: ''
position: absolute
top: 50%
right: 4px
width: 0
height: 0
border-left: 8px solid transparent
border-right: 8px solid transparent
border-bottom: 8px solid rgba(255, 255, 255, 0.2)
.table__th.presorted.sorted-asc::before,
.table__th.presorted.sorted-desc::after
border-bottom-color: white !important
.table__th.presorted::before
transform: translateY(150%) scale(1, -1)
transform-origin: top
.table__th.presorted::after
transform: translateY(-150%)
\:root \:root
--color-grey-light: #efefef --color-grey-light: #efefef
--color-grey-lighter: #f5f5f5 --color-grey-lighter: #f5f5f5
@ -1044,6 +1071,22 @@ th, td
border-right: 8px solid transparent border-right: 8px solid transparent
border-bottom: 8px solid rgba(255, 255, 255, 0.4) border-bottom: 8px solid rgba(255, 255, 255, 0.4)
.inactive-course-header::before,
.inactive-course-header::after
content: ''
position: absolute
right: 10px
top: 20px
width: 0
height: 0
border-left: 8px solid transparent
border-right: 8px solid transparent
border-bottom: 8px solid rgba(255,255,255, 0.2)
.inactive-course-header.sorted-asc::before,
.inactive-course-header.sorted-desc::after
border-bottom-color: white !important
.course-header::before .course-header::before
// magic numbers to move arrow back in the right position after flipping it. // magic numbers to move arrow back in the right position after flipping it.
this allows us to use the same border for the up and the down arrow this allows us to use the same border for the up and the down arrow
@ -1053,6 +1096,12 @@ th, td
.course-header::after .course-header::after
transform: translateY(-150%) transform: translateY(-150%)
.inactive-course-header::before
transform: translateY(150%) scale(1, -1)
transform-origin: top
.inactive-course-header::after
transform: translateY(-150%)
.course-header:hover::before, .course-header:hover::before,
.course-header:hover::after .course-header:hover::after
border-bottom-color: rgba(255, 255, 255, 0.7) border-bottom-color: rgba(255, 255, 255, 0.7)

View File

@ -355,6 +355,9 @@ export class AsyncTable {
_linkClickHandler = (event) => { _linkClickHandler = (event) => {
event.preventDefault(); event.preventDefault();
let url = this._getClickDestination(event.target); let url = this._getClickDestination(event.target);
if (!url)
return;
if (!url.match(/^http/)) { if (!url.match(/^http/)) {
url = window.location.origin + window.location.pathname + url; url = window.location.origin + window.location.pathname + url;
} }
@ -363,7 +366,7 @@ export class AsyncTable {
_getClickDestination(el) { _getClickDestination(el) {
if (!el.matches('a') && !el.querySelector('a')) { if (!el.matches('a') && !el.querySelector('a')) {
return ''; return null;
} }
return el.getAttribute('href') || el.querySelector('a').getAttribute('href'); return el.getAttribute('href') || el.querySelector('a').getAttribute('href');
} }

View File

@ -216,7 +216,10 @@
line-height: 1.4 line-height: 1.4
max-width: 85vw max-width: 85vw
.course-header .explanation
clear: both
.course-header, .inactive-course-header
float: left float: left
background-color: var(--color-dark) background-color: var(--color-dark)
position: relative position: relative

View File

@ -3182,3 +3182,5 @@ WGFTextInput: Textfeld
WGFFileUpload: Dateifeld WGFFileUpload: Dateifeld
WorkflowGraphFormUploadIsDirectory: Upload ist Verzeichnis WorkflowGraphFormUploadIsDirectory: Upload ist Verzeichnis
WorkflowGraphFormInvalidNumberOfFiles: Es muss genau eine Datei hochgeladen werden WorkflowGraphFormInvalidNumberOfFiles: Es muss genau eine Datei hochgeladen werden
CourseSortingOnlyLoggedIn: Das Benutzerinterface zur Sortierung dieser Tabelle ist nur für eingeloggte Benutzer aktiv

View File

@ -3182,3 +3182,5 @@ WGFTextInput: Text field
WGFFileUpload: File field WGFFileUpload: File field
WorkflowGraphFormUploadIsDirectory: Upload is a directory WorkflowGraphFormUploadIsDirectory: Upload is a directory
WorkflowGraphFormInvalidNumberOfFiles: You need to upload exactly one file WorkflowGraphFormInvalidNumberOfFiles: You need to upload exactly one file
CourseSortingOnlyLoggedIn: The user interface for sorting this table is only active for logged in users

View File

@ -897,6 +897,10 @@ instance IsDBTable m a => IsString (DBCell m a) where
-- | DB-backed tables with pagination, may short-circuit a handler if the frontend only asks for the table content, i.e. handler actions after calls to dbTable may not happen at all. -- | DB-backed tables with pagination, may short-circuit a handler if the frontend only asks for the table content, i.e. handler actions after calls to dbTable may not happen at all.
dbTable :: forall m x. IsDBTable m x => PSValidator m x -> DBTable m x -> DB (DBResult m x) dbTable :: forall m x. IsDBTable m x => PSValidator m x -> DBTable m x -> DB (DBResult m x)
dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> dbtIdent), dbtStyle = DBStyle{..}, .. } = do dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> dbtIdent), dbtStyle = DBStyle{..}, .. } = do
doSorting <- or2M
(getsYesod . views _appBotMitigations $ Set.notMember SettingBotMitigationOnlyLoggedInTableSorting)
(is _Just <$> maybeAuthId)
let let
sortingOptions = mkOptionList sortingOptions = mkOptionList
[ Option t' (SortingSetting t d) t' [ Option t' (SortingSetting t d) t'
@ -951,6 +955,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
psShortcircuit <- (== Just dbtIdent') <$> lookupCustomHeader HeaderDBTableShortcircuit psShortcircuit <- (== Just dbtIdent') <$> lookupCustomHeader HeaderDBTableShortcircuit
let let
-- adjustPI = over _piSorting $ guardOnM doSorting -- probably not neccessary; not displaying the links should be enough for now
((errs, PaginationSettings{..}), paginationInput@PaginationInput{..}) ((errs, PaginationSettings{..}), paginationInput@PaginationInput{..})
| FormSuccess pi <- piResult | FormSuccess pi <- piResult
, not $ piIsUnset pi , not $ piIsUnset pi

View File

@ -77,6 +77,8 @@ import Data.Conduit.Algorithms.FastCDC
import Utils.Lens.TH import Utils.Lens.TH
import qualified Data.Set as Set
-- | Runtime settings to configure this application. These settings can be -- | Runtime settings to configure this application. These settings can be
-- loaded from various sources: defaults, environment variables, config files, -- loaded from various sources: defaults, environment variables, config files,
@ -217,6 +219,8 @@ data AppSettings = AppSettings
, appFileSourceARCConf :: Maybe (ARCConf Int) , appFileSourceARCConf :: Maybe (ARCConf Int)
, appFileSourcePrewarmConf :: Maybe PrewarmCacheConf , appFileSourcePrewarmConf :: Maybe PrewarmCacheConf
, appBotMitigations :: Set SettingBotMitigation
} deriving Show } deriving Show
data JobMode = JobsLocal { jobsAcceptOffload :: Bool } data JobMode = JobsLocal { jobsAcceptOffload :: Bool }
@ -352,6 +356,11 @@ data PrewarmCacheConf = PrewarmCacheConf
, precMaxSpeedup :: Rational , precMaxSpeedup :: Rational
} deriving (Eq, Ord, Read, Show, Generic, Typeable) } deriving (Eq, Ord, Read, Show, Generic, Typeable)
data SettingBotMitigation
= SettingBotMitigationOnlyLoggedInTableSorting
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
deriving anyclass (Universe, Finite)
nullaryPathPiece ''ApprootScope $ camelToPathPiece' 1 nullaryPathPiece ''ApprootScope $ camelToPathPiece' 1
pathPieceJSON ''ApprootScope pathPieceJSON ''ApprootScope
pathPieceJSONKey ''ApprootScope pathPieceJSONKey ''ApprootScope
@ -388,6 +397,10 @@ deriveJSON defaultOptions
makeLenses_ ''PrewarmCacheConf makeLenses_ ''PrewarmCacheConf
nullaryPathPiece ''SettingBotMitigation $ camelToPathPiece' 3
pathPieceJSON ''SettingBotMitigation
pathPieceJSONKey ''SettingBotMitigation
instance FromJSON LdapConf where instance FromJSON LdapConf where
parseJSON = withObject "LdapConf" $ \o -> do parseJSON = withObject "LdapConf" $ \o -> do
ldapTls <- o .:? "tls" ldapTls <- o .:? "tls"
@ -660,6 +673,8 @@ instance FromJSON AppSettings where
] ]
appFileSourcePrewarmConf <- over (_Just . _precInhibit) (max 0) . assertM isValidPrewarmConf <$> o .:? "file-source-prewarm" appFileSourcePrewarmConf <- over (_Just . _precInhibit) (max 0) . assertM isValidPrewarmConf <$> o .:? "file-source-prewarm"
appBotMitigations <- o .:? "bot-mitigations" .!= Set.empty
return AppSettings{..} return AppSettings{..}
makeClassy_ ''AppSettings makeClassy_ ''AppSettings

View File

@ -1,12 +1,15 @@
$newline never $newline never
<th .table__th *{attrs} :isSortable:.sortable :isSorted SortAsc:.sorted-asc :isSorted SortDesc:.sorted-desc :is _Just sortableKey:uw-hide-column-header=#{maybe "" toPathPiece sortableKey} :cellSize /= 1:colspan=#{cellSize}> <th .table__th *{attrs} :isSortable && doSorting:.sortable :isSortable && not doSorting:.presorted :isSorted SortAsc:.sorted-asc :isSorted SortDesc:.sorted-desc :is _Just sortableKey:uw-hide-column-header=#{maybe "" toPathPiece sortableKey} :cellSize /= 1:colspan=#{cellSize}>
$maybe flag <- sortableKey $if doSorting
$case directions $maybe flag <- sortableKey
$of [SortAsc] $case directions
<a .table__th-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortDesc : piSorting')) . substPi}> $of [SortAsc]
^{widget} <a .table__th-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortDesc : piSorting')) . substPi}>
$of _ ^{widget}
<a .table__th-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortAsc : piSorting')) . substPi}> $of _
^{widget} <a .table__th-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortAsc : piSorting')) . substPi}>
$nothing ^{widget}
$nothing
^{widget}
$else
^{widget} ^{widget}

View File

@ -3,6 +3,9 @@ $newline never
$maybe wdgt <- wHeaders $maybe wdgt <- wHeaders
<div .course-teaser-header> <div .course-teaser-header>
^{wdgt} ^{wdgt}
$if not doSorting
<p .explanation>
_{MsgCourseSortingOnlyLoggedIn}
$if null wRows && (dbsEmptyStyle == DBESHeading) $if null wRows && (dbsEmptyStyle == DBESHeading)
<p>_{dbsEmptyMessage} <p>_{dbsEmptyMessage}
$else $else

View File

@ -1,12 +1,14 @@
$newline never $newline never
$maybe flag <- sortableKey $maybe flag <- sortableKey
<span .course-header *{attrs} :isSorted SortAsc:.sorted-asc :isSorted SortDesc:.sorted-desc> $if doSorting
$case directions <span .course-header *{attrs} :isSorted SortAsc:.sorted-asc :isSorted SortDesc:.sorted-desc>
$of [SortAsc] $case directions
<a .course-header-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortDesc : piSorting'))}> $of [SortAsc]
^{widget} <a .course-header-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortDesc : piSorting'))}>
$of _ ^{widget}
<a .course-header-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortAsc : piSorting'))}> $of _
^{widget} <a .course-header-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortAsc : piSorting'))}>
$nothing ^{widget}
$else
<span .inactive-course-header *{attrs} :isSorted SortAsc:.sorted-asc :isSorted SortDesc:.sorted-desc>
^{widget}