From fb6ae089c63174edc1d84512ea35378ab8cd0e0e Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Sun, 21 Feb 2021 13:39:47 +0100 Subject: [PATCH] feat(bot-mitigations): only logged in table sorting --- config/settings.yml | 3 ++ frontend/src/app.sass | 49 +++++++++++++++++++ frontend/src/utils/async-table/async-table.js | 5 +- .../utils/course-teaser/course-teaser.sass | 5 +- messages/uniworx/de-de-formal.msg | 4 +- messages/uniworx/en-eu.msg | 2 + src/Handler/Utils/Table/Pagination.hs | 5 ++ src/Settings.hs | 15 ++++++ templates/table/cell/header.hamlet | 23 +++++---- templates/table/course/colonnade.hamlet | 3 ++ templates/table/course/header.hamlet | 22 +++++---- 11 files changed, 113 insertions(+), 23 deletions(-) diff --git a/config/settings.yml b/config/settings.yml index 7c6181cc9..ea6d0dd97 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -281,3 +281,6 @@ file-source-prewarm: inhibit: 3600 # 60m steps: 20 max-speedup: 3 + +bot-mitigations: + - only-logged-in-table-sorting diff --git a/frontend/src/app.sass b/frontend/src/app.sass index 365ac1f3e..0156fee16 100644 --- a/frontend/src/app.sass +++ b/frontend/src/app.sass @@ -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) diff --git a/frontend/src/utils/async-table/async-table.js b/frontend/src/utils/async-table/async-table.js index 52ee85e02..3dbaad95e 100644 --- a/frontend/src/utils/async-table/async-table.js +++ b/frontend/src/utils/async-table/async-table.js @@ -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'); } diff --git a/frontend/src/utils/course-teaser/course-teaser.sass b/frontend/src/utils/course-teaser/course-teaser.sass index 56831de4f..d8866b21f 100644 --- a/frontend/src/utils/course-teaser/course-teaser.sass +++ b/frontend/src/utils/course-teaser/course-teaser.sass @@ -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 diff --git a/messages/uniworx/de-de-formal.msg b/messages/uniworx/de-de-formal.msg index 030c292fc..3dd02a5f9 100644 --- a/messages/uniworx/de-de-formal.msg +++ b/messages/uniworx/de-de-formal.msg @@ -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 \ No newline at end of file +WorkflowGraphFormInvalidNumberOfFiles: Es muss genau eine Datei hochgeladen werden + +CourseSortingOnlyLoggedIn: Das Benutzerinterface zur Sortierung dieser Tabelle ist nur für eingeloggte Benutzer aktiv \ No newline at end of file diff --git a/messages/uniworx/en-eu.msg b/messages/uniworx/en-eu.msg index d4dab1a62..0ab958e2c 100644 --- a/messages/uniworx/en-eu.msg +++ b/messages/uniworx/en-eu.msg @@ -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 diff --git a/src/Handler/Utils/Table/Pagination.hs b/src/Handler/Utils/Table/Pagination.hs index 11748f778..6c345303d 100644 --- a/src/Handler/Utils/Table/Pagination.hs +++ b/src/Handler/Utils/Table/Pagination.hs @@ -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 diff --git a/src/Settings.hs b/src/Settings.hs index 83846b1aa..9006adba0 100644 --- a/src/Settings.hs +++ b/src/Settings.hs @@ -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 diff --git a/templates/table/cell/header.hamlet b/templates/table/cell/header.hamlet index ecc5ab994..97f08b3a1 100644 --- a/templates/table/cell/header.hamlet +++ b/templates/table/cell/header.hamlet @@ -1,12 +1,15 @@ $newline never - - $maybe flag <- sortableKey - $case directions - $of [SortAsc] - - ^{widget} - $of _ - - ^{widget} - $nothing + + $if doSorting + $maybe flag <- sortableKey + $case directions + $of [SortAsc] + + ^{widget} + $of _ + + ^{widget} + $nothing + ^{widget} + $else ^{widget} diff --git a/templates/table/course/colonnade.hamlet b/templates/table/course/colonnade.hamlet index d23c64020..f3436055d 100644 --- a/templates/table/course/colonnade.hamlet +++ b/templates/table/course/colonnade.hamlet @@ -3,6 +3,9 @@ $newline never $maybe wdgt <- wHeaders
^{wdgt} + $if not doSorting +

+ _{MsgCourseSortingOnlyLoggedIn} $if null wRows && (dbsEmptyStyle == DBESHeading)

_{dbsEmptyMessage} $else diff --git a/templates/table/course/header.hamlet b/templates/table/course/header.hamlet index 23ccf4a10..3970c200a 100644 --- a/templates/table/course/header.hamlet +++ b/templates/table/course/header.hamlet @@ -1,12 +1,14 @@ $newline never - $maybe flag <- sortableKey - - $case directions - $of [SortAsc] - - ^{widget} - $of _ - - ^{widget} -$nothing + $if doSorting + + $case directions + $of [SortAsc] + + ^{widget} + $of _ + + ^{widget} + $else + + ^{widget}