Skip to content

Commit a5a2f99

Browse files
authored
SQSERVICES-1375-block-bot-api-if-2-fa-is-enabled (#2207)
1 parent d50c030 commit a5a2f99

File tree

7 files changed

+195
-8
lines changed

7 files changed

+195
-8
lines changed

changelog.d/2-features/pr-2207

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The bot API will be blocked if the 2nd factor authentication team feature is enabled. Please refer to [/docs/reference/config-options.md#2nd-factor-password-challenge](https://github.com/wireapp/wire-server/blob/develop/docs/reference/config-options.md#2nd-factor-password-challenge).

services/brig/src/Brig/IO/Intra.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ module Brig.IO.Intra
5858
changeTeamStatus,
5959
getTeamSearchVisibility,
6060
getVerificationCodeEnabled,
61+
getTeamFeatureStatusSndFactorPasswordChallenge,
6162

6263
-- * Legalhold
6364
guardLegalhold,
@@ -1006,6 +1007,15 @@ getVerificationCodeEnabled tid = do
10061007
paths ["i", "teams", toByteString' tid, "features", toByteString' TeamFeatureSndFactorPasswordChallenge]
10071008
. expect2xx
10081009

1010+
getTeamFeatureStatusSndFactorPasswordChallenge :: Maybe UserId -> (AppIO r) TeamFeatureStatusNoConfig
1011+
getTeamFeatureStatusSndFactorPasswordChallenge mbUserId =
1012+
responseJsonUnsafe
1013+
<$> galleyRequest
1014+
GET
1015+
( paths ["i", "feature-configs", toByteString' (knownTeamFeatureName @'TeamFeatureSndFactorPasswordChallenge)]
1016+
. maybe id (queryItem "user_id" . toByteString') mbUserId
1017+
)
1018+
10091019
-- | Calls 'Galley.API.updateTeamStatusH'.
10101020
changeTeamStatus :: TeamId -> Team.TeamStatus -> Maybe Currency.Alpha -> (AppIO r) ()
10111021
changeTeamStatus tid s cur = do

services/brig/src/Brig/Provider/API.hs

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ import qualified Wire.API.Provider as Public
103103
import qualified Wire.API.Provider.Bot as Public (BotUserView)
104104
import qualified Wire.API.Provider.Service as Public
105105
import qualified Wire.API.Provider.Service.Tag as Public
106+
import qualified Wire.API.Team.Feature as Feature
106107
import Wire.API.Team.LegalHold (LegalholdProtectee (UnprotectedBot))
107108
import qualified Wire.API.User as Public (UserProfile, publicProfile)
108109
import qualified Wire.API.User.Client as Public (Client, ClientCapability (ClientSupportsLegalholdImplicitConsent), PubClient (..), UserClientPrekeyMap, UserClients, userClients)
@@ -319,6 +320,7 @@ routesInternal = do
319320

320321
newAccountH :: JsonRequest Public.NewProvider -> (Handler r) Response
321322
newAccountH req = do
323+
guardSecondFactorDisabled Nothing
322324
setStatus status201 . json <$> (newAccount =<< parseJsonBody req)
323325

324326
newAccount :: Public.NewProvider -> (Handler r) Public.NewProviderResponse
@@ -355,6 +357,7 @@ newAccount new = do
355357

356358
activateAccountKeyH :: Code.Key ::: Code.Value -> (Handler r) Response
357359
activateAccountKeyH (key ::: val) = do
360+
guardSecondFactorDisabled Nothing
358361
maybe (setStatus status204 empty) json <$> activateAccountKey key val
359362

360363
activateAccountKey :: Code.Key -> Code.Value -> (Handler r) (Maybe Public.ProviderActivationResponse)
@@ -381,6 +384,7 @@ activateAccountKey key val = do
381384

382385
getActivationCodeH :: Public.Email -> (Handler r) Response
383386
getActivationCodeH e = do
387+
guardSecondFactorDisabled Nothing
384388
json <$> getActivationCode e
385389

386390
getActivationCode :: Public.Email -> (Handler r) FoundActivationCode
@@ -401,6 +405,7 @@ instance ToJSON FoundActivationCode where
401405

402406
approveAccountKeyH :: Code.Key ::: Code.Value -> (Handler r) Response
403407
approveAccountKeyH (key ::: val) = do
408+
guardSecondFactorDisabled Nothing
404409
empty <$ approveAccountKey key val
405410

406411
approveAccountKey :: Code.Key -> Code.Value -> (Handler r) ()
@@ -415,6 +420,7 @@ approveAccountKey key val = do
415420

416421
loginH :: JsonRequest Public.ProviderLogin -> (Handler r) Response
417422
loginH req = do
423+
guardSecondFactorDisabled Nothing
418424
tok <- login =<< parseJsonBody req
419425
setProviderCookie tok empty
420426

@@ -428,6 +434,7 @@ login l = do
428434

429435
beginPasswordResetH :: JsonRequest Public.PasswordReset -> (Handler r) Response
430436
beginPasswordResetH req = do
437+
guardSecondFactorDisabled Nothing
431438
setStatus status201 empty <$ (beginPasswordReset =<< parseJsonBody req)
432439

433440
beginPasswordReset :: Public.PasswordReset -> (Handler r) ()
@@ -449,6 +456,7 @@ beginPasswordReset (Public.PasswordReset target) = do
449456

450457
completePasswordResetH :: JsonRequest Public.CompletePasswordReset -> (Handler r) Response
451458
completePasswordResetH req = do
459+
guardSecondFactorDisabled Nothing
452460
empty <$ (completePasswordReset =<< parseJsonBody req)
453461

454462
completePasswordReset :: Public.CompletePasswordReset -> (Handler r) ()
@@ -469,6 +477,7 @@ completePasswordReset (Public.CompletePasswordReset key val newpwd) = do
469477

470478
getAccountH :: ProviderId -> (Handler r) Response
471479
getAccountH pid = do
480+
guardSecondFactorDisabled Nothing
472481
getAccount pid <&> \case
473482
Just p -> json p
474483
Nothing -> setStatus status404 empty
@@ -478,6 +487,7 @@ getAccount = wrapClientE . DB.lookupAccount
478487

479488
updateAccountProfileH :: ProviderId ::: JsonRequest Public.UpdateProvider -> (Handler r) Response
480489
updateAccountProfileH (pid ::: req) = do
490+
guardSecondFactorDisabled Nothing
481491
empty <$ (updateAccountProfile pid =<< parseJsonBody req)
482492

483493
updateAccountProfile :: ProviderId -> Public.UpdateProvider -> (Handler r) ()
@@ -492,6 +502,7 @@ updateAccountProfile pid upd = do
492502

493503
updateAccountEmailH :: ProviderId ::: JsonRequest Public.EmailUpdate -> (Handler r) Response
494504
updateAccountEmailH (pid ::: req) = do
505+
guardSecondFactorDisabled Nothing
495506
setStatus status202 empty <$ (updateAccountEmail pid =<< parseJsonBody req)
496507

497508
updateAccountEmail :: ProviderId -> Public.EmailUpdate -> (Handler r) ()
@@ -514,6 +525,7 @@ updateAccountEmail pid (Public.EmailUpdate new) = do
514525

515526
updateAccountPasswordH :: ProviderId ::: JsonRequest Public.PasswordChange -> (Handler r) Response
516527
updateAccountPasswordH (pid ::: req) = do
528+
guardSecondFactorDisabled Nothing
517529
empty <$ (updateAccountPassword pid =<< parseJsonBody req)
518530

519531
updateAccountPassword :: ProviderId -> Public.PasswordChange -> (Handler r) ()
@@ -527,6 +539,7 @@ updateAccountPassword pid upd = do
527539

528540
addServiceH :: ProviderId ::: JsonRequest Public.NewService -> (Handler r) Response
529541
addServiceH (pid ::: req) = do
542+
guardSecondFactorDisabled Nothing
530543
setStatus status201 . json <$> (addService pid =<< parseJsonBody req)
531544

532545
addService :: ProviderId -> Public.NewService -> (Handler r) Public.NewServiceResponse
@@ -546,13 +559,16 @@ addService pid new = do
546559
return $ Public.NewServiceResponse sid rstoken
547560

548561
listServicesH :: ProviderId -> (Handler r) Response
549-
listServicesH pid = json <$> listServices pid
562+
listServicesH pid = do
563+
guardSecondFactorDisabled Nothing
564+
json <$> listServices pid
550565

551566
listServices :: ProviderId -> (Handler r) [Public.Service]
552567
listServices = wrapClientE . DB.listServices
553568

554569
getServiceH :: ProviderId ::: ServiceId -> (Handler r) Response
555570
getServiceH (pid ::: sid) = do
571+
guardSecondFactorDisabled Nothing
556572
json <$> getService pid sid
557573

558574
getService :: ProviderId -> ServiceId -> (Handler r) Public.Service
@@ -561,6 +577,7 @@ getService pid sid =
561577

562578
updateServiceH :: ProviderId ::: ServiceId ::: JsonRequest Public.UpdateService -> (Handler r) Response
563579
updateServiceH (pid ::: sid ::: req) = do
580+
guardSecondFactorDisabled Nothing
564581
empty <$ (updateService pid sid =<< parseJsonBody req)
565582

566583
updateService :: ProviderId -> ServiceId -> Public.UpdateService -> (Handler r) ()
@@ -593,6 +610,7 @@ updateService pid sid upd = do
593610

594611
updateServiceConnH :: ProviderId ::: ServiceId ::: JsonRequest Public.UpdateServiceConn -> (Handler r) Response
595612
updateServiceConnH (pid ::: sid ::: req) = do
613+
guardSecondFactorDisabled Nothing
596614
empty <$ (updateServiceConn pid sid =<< parseJsonBody req)
597615

598616
updateServiceConn :: ProviderId -> ServiceId -> Public.UpdateServiceConn -> (Handler r) ()
@@ -638,6 +656,7 @@ updateServiceConn pid sid upd = do
638656
-- delete the service. See 'finishDeleteService'.
639657
deleteServiceH :: ProviderId ::: ServiceId ::: JsonRequest Public.DeleteService -> (Handler r) Response
640658
deleteServiceH (pid ::: sid ::: req) = do
659+
guardSecondFactorDisabled Nothing
641660
setStatus status202 empty <$ (deleteService pid sid =<< parseJsonBody req)
642661

643662
-- | The endpoint that is called to delete a service.
@@ -676,6 +695,7 @@ finishDeleteService pid sid = do
676695

677696
deleteAccountH :: ProviderId ::: JsonRequest Public.DeleteProvider -> (Handler r) Response
678697
deleteAccountH (pid ::: req) = do
698+
guardSecondFactorDisabled Nothing
679699
empty <$ (deleteAccount pid =<< parseJsonBody req)
680700

681701
deleteAccount :: ProviderId -> Public.DeleteProvider -> (Handler r) ()
@@ -700,6 +720,7 @@ deleteAccount pid del = do
700720

701721
getProviderProfileH :: ProviderId -> (Handler r) Response
702722
getProviderProfileH pid = do
723+
guardSecondFactorDisabled Nothing
703724
json <$> getProviderProfile pid
704725

705726
getProviderProfile :: ProviderId -> (Handler r) Public.ProviderProfile
@@ -708,13 +729,15 @@ getProviderProfile pid =
708729

709730
listServiceProfilesH :: ProviderId -> (Handler r) Response
710731
listServiceProfilesH pid = do
732+
guardSecondFactorDisabled Nothing
711733
json <$> listServiceProfiles pid
712734

713735
listServiceProfiles :: ProviderId -> (Handler r) [Public.ServiceProfile]
714736
listServiceProfiles = wrapClientE . DB.listServiceProfiles
715737

716738
getServiceProfileH :: ProviderId ::: ServiceId -> (Handler r) Response
717739
getServiceProfileH (pid ::: sid) = do
740+
guardSecondFactorDisabled Nothing
718741
json <$> getServiceProfile pid sid
719742

720743
getServiceProfile :: ProviderId -> ServiceId -> (Handler r) Public.ServiceProfile
@@ -723,6 +746,7 @@ getServiceProfile pid sid =
723746

724747
searchServiceProfilesH :: Maybe (Public.QueryAnyTags 1 3) ::: Maybe Text ::: Range 10 100 Int32 -> (Handler r) Response
725748
searchServiceProfilesH (qt ::: start ::: size) = do
749+
guardSecondFactorDisabled Nothing
726750
json <$> searchServiceProfiles qt start size
727751

728752
-- TODO: in order to actually make it possible for clients to implement
@@ -742,6 +766,7 @@ searchTeamServiceProfilesH ::
742766
UserId ::: TeamId ::: Maybe (Range 1 128 Text) ::: Bool ::: Range 10 100 Int32 ->
743767
(Handler r) Response
744768
searchTeamServiceProfilesH (uid ::: tid ::: prefix ::: filterDisabled ::: size) = do
769+
guardSecondFactorDisabled (Just uid)
745770
json <$> searchTeamServiceProfiles uid tid prefix filterDisabled size
746771

747772
-- NB: unlike 'searchServiceProfiles', we don't filter by service provider here
@@ -763,7 +788,9 @@ searchTeamServiceProfiles uid tid prefix filterDisabled size = do
763788
wrapClientE $ DB.paginateServiceWhitelist tid prefix filterDisabled (fromRange size)
764789

765790
getServiceTagListH :: () -> (Handler r) Response
766-
getServiceTagListH () = json <$> getServiceTagList ()
791+
getServiceTagListH () = do
792+
guardSecondFactorDisabled Nothing
793+
json <$> getServiceTagList ()
767794

768795
getServiceTagList :: () -> Monad m => m Public.ServiceTagList
769796
getServiceTagList () = return (Public.ServiceTagList allTags)
@@ -772,6 +799,7 @@ getServiceTagList () = return (Public.ServiceTagList allTags)
772799

773800
updateServiceWhitelistH :: UserId ::: ConnId ::: TeamId ::: JsonRequest Public.UpdateServiceWhitelist -> (Handler r) Response
774801
updateServiceWhitelistH (uid ::: con ::: tid ::: req) = do
802+
guardSecondFactorDisabled (Just uid)
775803
resp <- updateServiceWhitelist uid con tid =<< parseJsonBody req
776804
let status = case resp of
777805
UpdateServiceWhitelistRespChanged -> status200
@@ -819,6 +847,7 @@ updateServiceWhitelist uid con tid upd = do
819847

820848
addBotH :: UserId ::: ConnId ::: ConvId ::: JsonRequest Public.AddBot -> (Handler r) Response
821849
addBotH (zuid ::: zcon ::: cid ::: req) = do
850+
guardSecondFactorDisabled (Just zuid)
822851
setStatus status201 . json <$> (addBot zuid zcon cid =<< parseJsonBody req)
823852

824853
addBot :: UserId -> ConnId -> ConvId -> Public.AddBot -> (Handler r) Public.AddBotResponse
@@ -897,6 +926,7 @@ addBot zuid zcon cid add = do
897926

898927
removeBotH :: UserId ::: ConnId ::: ConvId ::: BotId -> (Handler r) Response
899928
removeBotH (zusr ::: zcon ::: cid ::: bid) = do
929+
guardSecondFactorDisabled (Just zusr)
900930
maybe (setStatus status204 empty) json <$> removeBot zusr zcon cid bid
901931

902932
removeBot :: UserId -> ConnId -> ConvId -> BotId -> (Handler r) (Maybe Public.RemoveBotResponse)
@@ -918,7 +948,9 @@ removeBot zusr zcon cid bid = do
918948
-- Bot API
919949

920950
botGetSelfH :: BotId -> (Handler r) Response
921-
botGetSelfH bot = json <$> botGetSelf bot
951+
botGetSelfH bot = do
952+
guardSecondFactorDisabled (Just (botUserId bot))
953+
json <$> botGetSelf bot
922954

923955
botGetSelf :: BotId -> (Handler r) Public.UserProfile
924956
botGetSelf bot = do
@@ -927,6 +959,7 @@ botGetSelf bot = do
927959

928960
botGetClientH :: BotId -> (Handler r) Response
929961
botGetClientH bot = do
962+
guardSecondFactorDisabled (Just (botUserId bot))
930963
maybe (throwErrorDescriptionType @ClientNotFound) (pure . json) =<< lift (botGetClient bot)
931964

932965
botGetClient :: BotId -> (AppIO r) (Maybe Public.Client)
@@ -935,6 +968,7 @@ botGetClient bot =
935968

936969
botListPrekeysH :: BotId -> (Handler r) Response
937970
botListPrekeysH bot = do
971+
guardSecondFactorDisabled (Just (botUserId bot))
938972
json <$> botListPrekeys bot
939973

940974
botListPrekeys :: BotId -> (Handler r) [Public.PrekeyId]
@@ -946,6 +980,7 @@ botListPrekeys bot = do
946980

947981
botUpdatePrekeysH :: BotId ::: JsonRequest Public.UpdateBotPrekeys -> (Handler r) Response
948982
botUpdatePrekeysH (bot ::: req) = do
983+
guardSecondFactorDisabled (Just (botUserId bot))
949984
empty <$ (botUpdatePrekeys bot =<< parseJsonBody req)
950985

951986
botUpdatePrekeys :: BotId -> Public.UpdateBotPrekeys -> (Handler r) ()
@@ -959,6 +994,7 @@ botUpdatePrekeys bot upd = do
959994

960995
botClaimUsersPrekeysH :: JsonRequest Public.UserClients -> (Handler r) Response
961996
botClaimUsersPrekeysH req = do
997+
guardSecondFactorDisabled Nothing
962998
json <$> (botClaimUsersPrekeys =<< parseJsonBody req)
963999

9641000
botClaimUsersPrekeys :: Public.UserClients -> (Handler r) Public.UserClientPrekeyMap
@@ -970,6 +1006,7 @@ botClaimUsersPrekeys body = do
9701006

9711007
botListUserProfilesH :: List UserId -> (Handler r) Response
9721008
botListUserProfilesH uids = do
1009+
guardSecondFactorDisabled Nothing -- should we check all user ids?
9731010
json <$> botListUserProfiles uids
9741011

9751012
botListUserProfiles :: List UserId -> (Handler r) [Public.BotUserView]
@@ -979,6 +1016,7 @@ botListUserProfiles uids = do
9791016

9801017
botGetUserClientsH :: UserId -> (Handler r) Response
9811018
botGetUserClientsH uid = do
1019+
guardSecondFactorDisabled (Just uid)
9821020
json <$> lift (botGetUserClients uid)
9831021

9841022
botGetUserClients :: UserId -> (AppIO r) [Public.PubClient]
@@ -989,10 +1027,12 @@ botGetUserClients uid =
9891027

9901028
botDeleteSelfH :: BotId ::: ConvId -> (Handler r) Response
9911029
botDeleteSelfH (bid ::: cid) = do
1030+
guardSecondFactorDisabled (Just (botUserId bid))
9921031
empty <$ botDeleteSelf bid cid
9931032

9941033
botDeleteSelf :: BotId -> ConvId -> (Handler r) ()
9951034
botDeleteSelf bid cid = do
1035+
guardSecondFactorDisabled (Just (botUserId bid))
9961036
bot <- lift . wrapClient $ User.lookupUser NoPendingInvitations (botUserId bid)
9971037
_ <- maybeInvalidBot (userService =<< bot)
9981038
_ <- lift $ deleteBot (botUserId bid) Nothing bid cid
@@ -1001,6 +1041,13 @@ botDeleteSelf bid cid = do
10011041
--------------------------------------------------------------------------------
10021042
-- Utilities
10031043

1044+
-- | If second factor auth is enabled, make sure that end-points that don't support it, but should, are blocked completely.
1045+
-- (This is a workaround until we have 2FA for those end-points as well.)
1046+
guardSecondFactorDisabled :: Maybe UserId -> Handler r ()
1047+
guardSecondFactorDisabled mbUserId = do
1048+
enabled <- lift $ (==) Feature.TeamFeatureEnabled . Feature.tfwoStatus <$> RPC.getTeamFeatureStatusSndFactorPasswordChallenge mbUserId
1049+
when enabled $ throwStd accessDenied
1050+
10041051
minRsaKeySize :: Int
10051052
minRsaKeySize = 256 -- Bytes (= 2048 bits)
10061053

@@ -1138,5 +1185,8 @@ serviceError :: RPC.ServiceError -> Wai.Error
11381185
serviceError RPC.ServiceUnavailable = badGateway
11391186
serviceError RPC.ServiceBotConflict = tooManyBots
11401187

1188+
accessDenied :: Wai.Error
1189+
accessDenied = Wai.mkError status403 "access-denied" "Access denied."
1190+
11411191
randServiceToken :: MonadIO m => m Public.ServiceToken
11421192
randServiceToken = ServiceToken . Ascii.encodeBase64Url <$> liftIO (randBytes 18)

0 commit comments

Comments
 (0)