Skip to content

Commit 6da289a

Browse files
authored
Fix reauth without password (#497)
* Fix: re-authentication for password-less users. * Fix: names. * Add a roundtrip unit test. * Fix: integration test behavior has changed. * Add galley integration tests for password-less users. * Add brig integration tests for password-less users.
1 parent 8881b52 commit 6da289a

File tree

13 files changed

+130
-77
lines changed

13 files changed

+130
-77
lines changed

libs/brig-types/src/Brig/Types/Intra.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,12 @@ instance ToJSON UserSet where
121121
-- | Certain operations might require reauth of the user. These are available
122122
-- only for users that have already set a password.
123123
newtype ReAuthUser = ReAuthUser
124-
{ reAuthPassword :: PlainTextPassword }
124+
{ reAuthPassword :: Maybe PlainTextPassword }
125+
deriving (Eq, Show)
125126

126127
instance FromJSON ReAuthUser where
127128
parseJSON = withObject "reauth-user" $ \o ->
128-
ReAuthUser <$> o .: "password"
129+
ReAuthUser <$> o .:? "password"
129130

130131
instance ToJSON ReAuthUser where
131132
toJSON ru = object

libs/brig-types/test/unit/Test/Brig/Types/Arbitrary.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module Test.Brig.Types.Arbitrary where
1616

1717
import Brig.Types.Activation
1818
import Brig.Types.Code
19+
import Brig.Types.Intra
1920
import Brig.Types.Provider (UpdateServiceWhitelist(..))
2021
import Brig.Types.TURN
2122
import Brig.Types.TURN.Internal
@@ -174,6 +175,9 @@ instance Arbitrary AsciiBase64Url where
174175
instance Arbitrary PlainTextPassword where
175176
arbitrary = PlainTextPassword . fromRange <$> genRangeText @6 @1024 arbitrary
176177

178+
instance Arbitrary ReAuthUser where
179+
arbitrary = ReAuthUser <$> arbitrary
180+
177181
instance Arbitrary DeleteUser where
178182
arbitrary = DeleteUser <$> arbitrary
179183

libs/brig-types/test/unit/Test/Brig/Types/User.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
module Test.Brig.Types.User where
1010

1111
import Brig.Types.Activation
12+
import Brig.Types.Intra
1213
import Brig.Types.Provider (UpdateServiceWhitelist)
1314
import Brig.Types.User
1415
import Data.Aeson
@@ -76,14 +77,15 @@ roundtripTests =
7677
, run @HandleUpdate Proxy
7778
, run @LocaleUpdate Proxy
7879
, run @NewPasswordReset Proxy
79-
, run @UserIdentity Proxy
8080
, run @NewUser Proxy
8181
, run @PasswordChange Proxy
8282
, run @PhoneRemove Proxy
8383
, run @PhoneUpdate Proxy
84+
, run @ReAuthUser Proxy
8485
, run @SelfProfile Proxy
8586
, run @UpdateServiceWhitelist Proxy
8687
, run @UserHandleInfo Proxy
88+
, run @UserIdentity Proxy
8789
, run @UserProfile Proxy
8890
, run @User Proxy
8991
, run @UserUpdate Proxy

libs/galley-types/src/Galley/Types/Teams.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,11 +247,11 @@ newtype NewTeamMember = NewTeamMember
247247
}
248248

249249
newtype TeamMemberDeleteData = TeamMemberDeleteData
250-
{ _tmdAuthPassword :: PlainTextPassword
250+
{ _tmdAuthPassword :: Maybe PlainTextPassword
251251
}
252252

253253
newtype TeamDeleteData = TeamDeleteData
254-
{ _tdAuthPassword :: PlainTextPassword
254+
{ _tdAuthPassword :: Maybe PlainTextPassword
255255
}
256256

257257
-- This is the cassandra timestamp of writetime(binding)
@@ -289,10 +289,10 @@ newEvent typ tid tme = Event typ tid tme Nothing
289289
newTeamUpdateData :: TeamUpdateData
290290
newTeamUpdateData = TeamUpdateData Nothing Nothing Nothing
291291

292-
newTeamMemberDeleteData :: PlainTextPassword -> TeamMemberDeleteData
292+
newTeamMemberDeleteData :: Maybe PlainTextPassword -> TeamMemberDeleteData
293293
newTeamMemberDeleteData = TeamMemberDeleteData
294294

295-
newTeamDeleteData :: PlainTextPassword -> TeamDeleteData
295+
newTeamDeleteData :: Maybe PlainTextPassword -> TeamDeleteData
296296
newTeamDeleteData = TeamDeleteData
297297

298298
makeLenses ''Team

services/brig/src/Brig/User/API/Auth.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ getLoginCode (_ ::: phone) = do
177177
reAuthUser :: JSON ::: UserId ::: Request -> Handler Response
178178
reAuthUser (_ ::: uid ::: req) = do
179179
body <- parseJsonBody req
180-
User.reauthenticate uid (Just $ reAuthPassword body) !>> reauthError
180+
User.reauthenticate uid (reAuthPassword body) !>> reauthError
181181
return empty
182182

183183
login :: Request ::: Bool ::: JSON ::: JSON -> Handler Response

services/brig/test/integration/API/Team.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -562,9 +562,9 @@ testCreateUserInternalSSO brig galley = do
562562
let ssoid = UserSSOId "nil" "nil"
563563

564564
-- creating users requires both sso_id and team_id
565-
postUser' False "dummy" True False (Just ssoid) Nothing brig
565+
postUser' True False "dummy" True False (Just ssoid) Nothing brig
566566
!!! const 400 === statusCode
567-
postUser' False "dummy" True False Nothing (Just teamid) brig
567+
postUser' True False "dummy" True False Nothing (Just teamid) brig
568568
!!! const 400 === statusCode
569569

570570
-- creating user with sso_id, team_id is ok

services/brig/test/integration/API/Team/Util.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ deleteTeam g tid u = do
151151
. paths ["teams", toByteString' tid]
152152
. zUser u
153153
. zConn "conn"
154-
. lbytes (encode $ Team.newTeamDeleteData Util.defPassword)
154+
. lbytes (encode $ Team.newTeamDeleteData $ Just Util.defPassword)
155155
) !!! const 202 === statusCode
156156

157157
getTeams :: UserId -> Galley -> Http Team.TeamList

services/brig/test/integration/API/User/Auth.hs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ tests conf m z b = testGroup "auth"
7676
, test m "logout" (testLogout b)
7777
]
7878
, testGroup "reauth"
79-
[ test m "reauthorisation" (testReauthorisation b)
79+
[ test m "reauthentication" (testReauthentication b)
8080
]
8181
]
8282

@@ -514,24 +514,26 @@ testLogout b = do
514514
post (b . path "/access" . cookie c) !!!
515515
const 403 === statusCode
516516

517-
testReauthorisation :: Brig -> Http ()
518-
testReauthorisation b = do
517+
testReauthentication :: Brig -> Http ()
518+
testReauthentication b = do
519519
u <- userId <$> randomUser b
520520

521521
let js = Http.body . RequestBodyLBS . encode $ object ["foo" .= ("bar" :: Text) ]
522522
get (b . paths [ "/i/users", toByteString' u, "reauthenticate"] . contentJson . js) !!! do
523-
const 400 === statusCode
523+
const 403 === statusCode
524+
-- it's ok to not give a password in the request body, but if the user has a password set,
525+
-- response will be `forbidden`.
524526

525-
get (b . paths [ "/i/users", toByteString' u, "reauthenticate"] . contentJson . payload (PlainTextPassword "123456")) !!! do
527+
get (b . paths [ "/i/users", toByteString' u, "reauthenticate"] . contentJson . payload (Just $ PlainTextPassword "123456")) !!! do
526528
const 403 === statusCode
527529
const (Just "invalid-credentials") === errorLabel
528530

529-
get (b . paths [ "/i/users", toByteString' u, "reauthenticate"] . contentJson . payload defPassword) !!! do
531+
get (b . paths [ "/i/users", toByteString' u, "reauthenticate"] . contentJson . payload (Just defPassword)) !!! do
530532
const 200 === statusCode
531533

532534
setStatus b u Suspended
533535

534-
get (b . paths [ "/i/users", toByteString' u, "reauthenticate"] . contentJson . payload defPassword) !!! do
536+
get (b . paths [ "/i/users", toByteString' u, "reauthenticate"] . contentJson . payload (Just defPassword)) !!! do
535537
const 403 === statusCode
536538
const (Just "suspended") === errorLabel
537539
where

services/brig/test/integration/API/User/Client.hs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,22 @@ tests :: ConnectionLimit -> Opt.Timeout -> Maybe Opt.Opts -> Manager -> Brig ->
3838
tests _cl _at _conf p b c g = testGroup "client"
3939
[ test p "get /users/:user/prekeys - 200" $ testGetUserPrekeys b
4040
, test p "get /users/:user/prekeys/:client - 200" $ testGetClientPrekey b
41-
, test p "post /clients - 201" $ testAddGetClient b c
41+
, test p "post /clients - 201 (pwd)" $ testAddGetClient True b c
42+
, test p "post /clients - 201 (no pwd)" $ testAddGetClient False b c
4243
, test p "post /clients - 403" $ testClientReauthentication b
4344
, test p "get /clients - 200" $ testListClients b
4445
, test p "get /clients/:client/prekeys - 200" $ testListPrekeyIds b
4546
, test p "post /clients - 400" $ testTooManyClients b
46-
, test p "delete /clients/:client - 200" $ testRemoveClient b c
47+
, test p "delete /clients/:client - 200 (pwd)" $ testRemoveClient True b c
48+
, test p "delete /clients/:client - 200 (no pwd)" $ testRemoveClient False b c
4749
, test p "put /clients/:client - 200" $ testUpdateClient b
4850
, test p "post /clients - 200 multiple temporary" $ testAddMultipleTemporary b g
4951
, test p "client/prekeys/race" $ testPreKeyRace b
5052
]
5153

52-
testAddGetClient :: Brig -> Cannon -> Http ()
53-
testAddGetClient brig cannon = do
54-
uid <- userId <$> randomUser brig
54+
testAddGetClient :: Bool -> Brig -> Cannon -> Http ()
55+
testAddGetClient hasPwd brig cannon = do
56+
uid <- userId <$> randomUser' hasPwd brig
5557
let rq = addClientReq brig uid (defNewClient TemporaryClient [somePrekeys !! 0] (someLastPrekeys !! 0))
5658
. header "X-Forwarded-For" "127.0.0.1" -- Fake IP to test IpAddr parsing.
5759
c <- WS.bracketR cannon uid $ \ws -> do
@@ -182,24 +184,28 @@ testTooManyClients brig = do
182184
const 403 === statusCode
183185
const (Just "too-many-clients") === fmap Error.label . decodeBody
184186

185-
testRemoveClient :: Brig -> Cannon -> Http ()
186-
testRemoveClient brig cannon = do
187-
u <- randomUser brig
187+
testRemoveClient :: Bool -> Brig -> Cannon -> Http ()
188+
testRemoveClient hasPwd brig cannon = do
189+
u <- randomUser' hasPwd brig
188190
let uid = userId u
189191
let Just email = userEmail u
190192

191193
-- Permanent client with attached cookie
192-
login brig (defEmailLogin email) PersistentCookie
193-
!!! const 200 === statusCode
194-
numCookies <- countCookies brig uid defCookieLabel
195-
liftIO $ Just 1 @=? numCookies
194+
when hasPwd $ do
195+
login brig (defEmailLogin email) PersistentCookie
196+
!!! const 200 === statusCode
197+
numCookies <- countCookies brig uid defCookieLabel
198+
liftIO $ Just 1 @=? numCookies
199+
196200
c <- decodeBody =<< addClient brig uid (client PermanentClient (someLastPrekeys !! 10))
197-
-- Missing password
198-
deleteClient brig uid (clientId c) Nothing !!! const 403 === statusCode
201+
202+
when hasPwd $ do
203+
-- Missing password
204+
deleteClient brig uid (clientId c) Nothing !!! const 403 === statusCode
199205

200206
-- Success
201207
WS.bracketR cannon uid $ \ws -> do
202-
deleteClient brig uid (clientId c) (Just defPassword)
208+
deleteClient brig uid (clientId c) (if hasPwd then Just defPassword else Nothing)
203209
!!! const 200 === statusCode
204210
void . liftIO $ WS.assertMatch (5 # Second) ws $ \n -> do
205211
let j = Object $ List1.head (ntfPayload n)

services/brig/test/integration/Util.hs

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -90,19 +90,25 @@ test' :: AWS.Env -> Manager -> TestName -> Http a -> TestTree
9090
test' e m n h = testCase n $ void $ runHttpT m (liftIO (purgeJournalQueue e) >> h)
9191

9292
randomUser :: HasCallStack => Brig -> Http User
93-
randomUser brig = do
93+
randomUser = randomUser' True
94+
95+
randomUser' :: HasCallStack => Bool -> Brig -> Http User
96+
randomUser' hasPwd brig = do
9497
n <- fromName <$> randomName
95-
createUser n brig
98+
createUser' hasPwd n brig
9699

97100
createUser :: HasCallStack => Text -> Brig -> Http User
98-
createUser name brig = do
99-
r <- postUser name True False Nothing Nothing brig <!!
101+
createUser = createUser' True
102+
103+
createUser' :: HasCallStack => Bool -> Text -> Brig -> Http User
104+
createUser' hasPwd name brig = do
105+
r <- postUser' hasPwd True name True False Nothing Nothing brig <!!
100106
const 201 === statusCode
101107
decodeBody r
102108

103109
createUserWithEmail :: HasCallStack => Text -> Email -> Brig -> Http User
104110
createUserWithEmail name email brig = do
105-
r <- postUserWithEmail True name (Just email) False Nothing Nothing brig <!!
111+
r <- postUserWithEmail True True name (Just email) False Nothing Nothing brig <!!
106112
const 201 === statusCode
107113
decodeBody r
108114

@@ -164,32 +170,32 @@ getConnection brig from to = get $ brig
164170

165171
-- | More flexible variant of 'createUser' (see above).
166172
postUser :: Text -> Bool -> Bool -> Maybe UserSSOId -> Maybe TeamId -> Brig -> Http ResponseLBS
167-
postUser = postUser' True
173+
postUser = postUser' True True
168174

169-
-- | Use @postUser' False@ instead of 'postUser' if you want to send broken bodies to test error
170-
-- messages.
171-
postUser' :: Bool -> Text -> Bool -> Bool -> Maybe UserSSOId -> Maybe TeamId -> Brig -> Http ResponseLBS
172-
postUser' validateBody name haveEmail havePhone ssoid teamid brig = do
175+
-- | Use @postUser' True False@ instead of 'postUser' if you want to send broken bodies to test error
176+
-- messages. Or @postUser' False True@ if you want to validate the body, but not set a password.
177+
postUser' :: Bool -> Bool -> Text -> Bool -> Bool -> Maybe UserSSOId -> Maybe TeamId -> Brig -> Http ResponseLBS
178+
postUser' hasPassword validateBody name haveEmail havePhone ssoid teamid brig = do
173179
email <- if haveEmail
174180
then Just <$> randomEmail
175181
else pure Nothing
176-
postUserWithEmail validateBody name email havePhone ssoid teamid brig
182+
postUserWithEmail hasPassword validateBody name email havePhone ssoid teamid brig
177183

178184
-- | More flexible variant of 'createUserUntrustedEmail' (see above).
179-
postUserWithEmail :: Bool -> Text -> Maybe Email -> Bool -> Maybe UserSSOId -> Maybe TeamId -> Brig -> Http ResponseLBS
180-
postUserWithEmail validateBody name email havePhone ssoid teamid brig = do
185+
postUserWithEmail :: Bool -> Bool -> Text -> Maybe Email -> Bool -> Maybe UserSSOId -> Maybe TeamId -> Brig -> Http ResponseLBS
186+
postUserWithEmail hasPassword validateBody name email havePhone ssoid teamid brig = do
181187
phone <- if havePhone
182188
then Just <$> randomPhone
183189
else pure Nothing
184-
let o = object
190+
let o = object $
185191
[ "name" .= name
186192
, "email" .= (fromEmail <$> email)
187193
, "phone" .= phone
188-
, "password" .= defPassword
189194
, "cookie" .= defCookieLabel
190195
, "sso_id" .= ssoid
191196
, "team_id" .= teamid
192-
]
197+
] <>
198+
[ "password" .= defPassword | hasPassword ]
193199
p = case Aeson.parse parseJSON o of
194200
Aeson.Success (p_ :: NewUser) -> p_
195201
bad -> error $ show (bad, o)

0 commit comments

Comments
 (0)