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
steps: 20
max-speedup: 3
bot-mitigations:
- only-logged-in-table-sorting

View File

@ -993,6 +993,11 @@ th, td
padding-right: 24px
cursor: pointer
.table__th.presorted,
.table__th.presorted
position: relative
padding-right: 24px
.table__th.sortable::after,
.table__th.sortable::before
content: ''
@ -1022,6 +1027,28 @@ th, td
.table__th.sorted-desc::after
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
--color-grey-light: #efefef
--color-grey-lighter: #f5f5f5
@ -1043,6 +1070,22 @@ th, td
border-left: 8px solid transparent
border-right: 8px solid transparent
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
// magic numbers to move arrow back in the right position after flipping it.
@ -1053,6 +1096,12 @@ th, td
.course-header::after
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::after
border-bottom-color: rgba(255, 255, 255, 0.7)

View File

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

View File

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

View File

@ -3181,4 +3181,6 @@ UrlFieldCouldNotParseAbsolute: Konnte nicht als absolute URL interpretiert werde
WGFTextInput: Textfeld
WGFFileUpload: Dateifeld
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
WorkflowGraphFormUploadIsDirectory: Upload is a directory
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.
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
doSorting <- or2M
(getsYesod . views _appBotMitigations $ Set.notMember SettingBotMitigationOnlyLoggedInTableSorting)
(is _Just <$> maybeAuthId)
let
sortingOptions = mkOptionList
[ Option t' (SortingSetting t d) t'
@ -951,6 +955,7 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
psShortcircuit <- (== Just dbtIdent') <$> lookupCustomHeader HeaderDBTableShortcircuit
let
-- adjustPI = over _piSorting $ guardOnM doSorting -- probably not neccessary; not displaying the links should be enough for now
((errs, PaginationSettings{..}), paginationInput@PaginationInput{..})
| FormSuccess pi <- piResult
, not $ piIsUnset pi

View File

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

View File

@ -1,12 +1,15 @@
$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}>
$maybe flag <- sortableKey
$case directions
$of [SortAsc]
<a .table__th-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortDesc : piSorting')) . substPi}>
^{widget}
$of _
<a .table__th-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortAsc : piSorting')) . substPi}>
^{widget}
$nothing
<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}>
$if doSorting
$maybe flag <- sortableKey
$case directions
$of [SortAsc]
<a .table__th-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortDesc : piSorting')) . substPi}>
^{widget}
$of _
<a .table__th-link rel=nofollow href=^{tblLink' $ setParams (wIdent "sorting") (map toPathPiece (SortingSetting flag SortAsc : piSorting')) . substPi}>
^{widget}
$nothing
^{widget}
$else
^{widget}

View File

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

View File

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