Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Work on Spar (#395)
- spar integration tests
- refactor errors in spar monad
- idp validation
- more idp management end-points
  • Loading branch information
fisx committed Jul 16, 2018
1 parent e9f993f commit 0505f8f
Show file tree
Hide file tree
Showing 31 changed files with 864 additions and 318 deletions.
15 changes: 12 additions & 3 deletions libs/bilge/src/Bilge/Request.hs
Expand Up @@ -26,7 +26,7 @@ module Bilge.Request
, showRequest
, noRedirect
, timeout
, expect2xx
, expect2xx, expect3xx, expect4xx
, checkStatus
, cookie
, cookieRaw
Expand Down Expand Up @@ -118,10 +118,19 @@ noRedirect :: Request -> Request
noRedirect r = r { Rq.redirectCount = 0 }

expect2xx :: Request -> Request
expect2xx r = r { Rq.checkResponse = check }
expect2xx = expectStatus ((== 2) . (`div` 100))

expect3xx :: Request -> Request
expect3xx = expectStatus ((== 3) . (`div` 100))

expect4xx :: Request -> Request
expect4xx = expectStatus ((== 4) . (`div` 100))

expectStatus :: (Int -> Bool) -> Request -> Request
expectStatus property r = r { Rq.checkResponse = check }
where
check _ res
| HTTP.statusCode (Rq.responseStatus res) `div` 100 == 2 = return ()
| property (HTTP.statusCode (Rq.responseStatus res)) = return ()
| otherwise = do
some <- Lazy.toStrict <$> brReadSome (Rq.responseBody res) 1024
throwHttp $ Rq.StatusCodeException (const () <$> res) some
Expand Down
2 changes: 1 addition & 1 deletion libs/galley-types/src/Galley/Types/Teams.hs
Expand Up @@ -195,7 +195,7 @@ data Perm =
| SetMemberPermissions
| GetTeamConversations
| DeleteTeam
deriving (Eq, Ord, Show)
deriving (Eq, Ord, Show, Enum, Bounded)

newtype BindingNewTeam = BindingNewTeam (NewTeam ())
deriving (Eq, Show)
Expand Down
14 changes: 7 additions & 7 deletions libs/types-common/src/Data/Misc.hs
Expand Up @@ -24,7 +24,7 @@ module Data.Misc
, Milliseconds (..)

-- * HttpsUrl
, HttpsUrl (..)
, HttpsUrl (..), mkHttpsUrl

-- * Fingerprint
, Fingerprint (..)
Expand All @@ -39,7 +39,6 @@ module Data.Misc

import Control.DeepSeq (NFData (..))
import Control.Lens ((^.), makeLenses)
import Control.Monad (when)
import Data.Aeson
import Data.ByteString (ByteString)
import Data.ByteString.Builder
Expand Down Expand Up @@ -197,18 +196,19 @@ newtype HttpsUrl = HttpsUrl
{ httpsUrl :: URIRef Absolute
} deriving Eq

mkHttpsUrl :: URIRef Absolute -> Either String HttpsUrl
mkHttpsUrl uri = if uri ^. uriSchemeL . schemeBSL == "https"
then Right $ HttpsUrl uri
else Left $ "Non-HTTPS URL: " ++ show uri

instance Show HttpsUrl where
showsPrec i = showsPrec i . httpsUrl

instance ToByteString HttpsUrl where
builder = serializeURIRef . httpsUrl

instance FromByteString HttpsUrl where
parser = do
u <- uriParser strictURIParserOptions
when (u^.uriSchemeL.schemeBSL /= "https") $
fail $ "Non-HTTPS URL: " ++ show u
return (HttpsUrl u)
parser = either fail pure . mkHttpsUrl =<< uriParser strictURIParserOptions

instance FromJSON HttpsUrl where
parseJSON = withText "HttpsUrl" $
Expand Down
20 changes: 18 additions & 2 deletions services/brig/src/Brig/API.hs
Expand Up @@ -192,10 +192,16 @@ sitemap o = do
post "/i/users/blacklist" (continue addBlacklist) $
param "email" ||| param "phone"

-- is :uid not team owner, or there are other team owners?
get "/i/users/:uid/can-be-deleted/:tid" (continue canBeDeleted) $
capture "uid"
.&. capture "tid"

-- is :uid team owner (the only one or one of several)?
get "/i/users/:uid/is-team-owner/:tid" (continue isTeamOwner) $
capture "uid"
.&. capture "tid"

post "/i/clients" (continue internalListClients) $
accept "application" "json"
.&. contentType "application" "json"
Expand Down Expand Up @@ -1377,16 +1383,26 @@ addBlacklist emailOrPhone = do

canBeDeleted :: UserId ::: TeamId -> Handler Response
canBeDeleted (uid ::: tid) = do
onlyOwner <- lift (Team.isOnlyTeamOwner uid tid)
onlyOwner <- lift (Team.teamOwnershipStatus uid tid)
case onlyOwner of
Team.IsOnlyTeamOwner -> throwStd noOtherOwner
Team.IsOneOfManyTeamOwners -> pure ()
Team.IsNotTeamOwner -> pure ()
Team.NoTeamOwnersAreLeft -> do
Team.NoTeamOwnersAreLeft -> do -- (keeping the user won't help in this case)
Log.warn $ Log.field "user" (toByteString uid)
. Log.msg (Log.val "Team.NoTeamOwnersAreLeft")
return empty

isTeamOwner :: UserId ::: TeamId -> Handler Response
isTeamOwner (uid ::: tid) = do
onlyOwner <- lift (Team.teamOwnershipStatus uid tid)
case onlyOwner of
Team.IsOnlyTeamOwner -> pure ()
Team.IsOneOfManyTeamOwners -> pure ()
Team.IsNotTeamOwner -> throwStd insufficientTeamPermissions
Team.NoTeamOwnersAreLeft -> throwStd insufficientTeamPermissions
return empty

deleteUser :: UserId ::: Request ::: JSON ::: JSON -> Handler Response
deleteUser (u ::: r ::: _ ::: _) = do
body <- parseJsonBody r
Expand Down
2 changes: 1 addition & 1 deletion services/brig/src/Brig/API/User.hs
Expand Up @@ -662,7 +662,7 @@ deleteUser uid pwd = do
ensureNotOnlyOwner acc = case userTeam . accountUser =<< acc of
Nothing -> pure ()
Just tid -> do
ownerSituation <- lift $ Team.isOnlyTeamOwner uid tid
ownerSituation <- lift $ Team.teamOwnershipStatus uid tid
case ownerSituation of
Team.IsOnlyTeamOwner -> throwE DeleteUserOnlyOwner
Team.IsOneOfManyTeamOwners -> pure ()
Expand Down
12 changes: 6 additions & 6 deletions services/brig/src/Brig/Team/Util.hs
Expand Up @@ -10,16 +10,16 @@ import Galley.Types.Teams
import qualified Brig.IO.Intra as Intra
import qualified Data.Set as Set

data IsOnlyTeamOwner = IsOnlyTeamOwner | IsOneOfManyTeamOwners | IsNotTeamOwner | NoTeamOwnersAreLeft
data TeamOwnershipStatus = IsOnlyTeamOwner | IsOneOfManyTeamOwners | IsNotTeamOwner | NoTeamOwnersAreLeft
deriving (Eq, Show, Bounded, Enum)

-- | A team owner is a team member with full permissions *and* an email address.
isOnlyTeamOwner :: UserId -> TeamId -> AppIO IsOnlyTeamOwner
isOnlyTeamOwner uid tid = isOnlyTeamOwner' uid . fmap (^. userId) <$> Intra.getTeamOwners tid
teamOwnershipStatus :: UserId -> TeamId -> AppIO TeamOwnershipStatus
teamOwnershipStatus uid tid = teamOwnershipStatus' uid . fmap (^. userId) <$> Intra.getTeamOwners tid

isOnlyTeamOwner' :: UserId -> [UserId] -> IsOnlyTeamOwner
isOnlyTeamOwner' _ [] = NoTeamOwnersAreLeft
isOnlyTeamOwner' uid (Set.fromList -> owners)
teamOwnershipStatus' :: UserId -> [UserId] -> TeamOwnershipStatus
teamOwnershipStatus' _ [] = NoTeamOwnersAreLeft
teamOwnershipStatus' uid (Set.fromList -> owners)
| uid `Set.notMember` owners = IsNotTeamOwner
| Set.null (Set.delete uid owners) = IsOnlyTeamOwner
| otherwise = IsOneOfManyTeamOwners
16 changes: 16 additions & 0 deletions services/brig/test/integration/API/Team.hs
Expand Up @@ -84,6 +84,7 @@ tests conf m b c g aws = do
, testGroup "sso"
[ test m "post /i/users - 201 internal-SSO" $ testCreateUserInternalSSO b g
, test m "delete /i/users/:id - 202 internal-SSO (ensure no orphan teams)" $ testDeleteUserSSO b g
, test m "get /i/users/:uid/is-team-owner/:tid" $ testSSOIsTeamOwner b g
]
]

Expand Down Expand Up @@ -481,6 +482,21 @@ testDeleteTeamUser brig galley = do
void $ retryWhileN 20 (/= Deleted) (getStatus brig invitee)
chkStatus brig invitee Deleted

testSSOIsTeamOwner :: Brig -> Galley -> Http ()
testSSOIsTeamOwner brig galley = do
(creator, tid) <- createUserWithTeam brig galley
stranger <- userId <$> randomUser brig
invitee <- userId <$> inviteAndRegisterUser creator tid brig

let check expectWhat uid = void $ get (brig . paths opath . expectWhat)
where opath = ["i", "users", toByteString' uid, "is-team-owner", toByteString' tid]

check expect2xx creator
check expect4xx stranger
check expect4xx invitee
updatePermissions creator tid (invitee, Team.fullPermissions) galley
check expect2xx invitee

testConnectionSameTeam :: Brig -> Galley -> Http ()
testConnectionSameTeam brig galley = do
(creatorA, tidA) <- createUserWithTeam brig galley
Expand Down
3 changes: 1 addition & 2 deletions services/integration.sh
Expand Up @@ -51,7 +51,6 @@ green=10
orange=3
yellow=11
purpleish=13
red=1

function run() {
service=$1
Expand All @@ -74,7 +73,7 @@ run galley ${yellow} Info
run gundeck ${blue} Info
run cannon ${orange} Info
run cargohold ${purpleish} Info
run spar ${red} Info
run spar ${orange} Info

# the ports are copied from ./integration.yaml
while [ "$all_services_are_up" == "" ]; do
Expand Down
14 changes: 11 additions & 3 deletions services/integration.yaml
Expand Up @@ -36,8 +36,16 @@ provider:
botPort: 9000

# Used by spar-integration
new_idp:
newIdp:
metadata: https://login.microsoftonline.com/682febe8-021b-4fde-ac09-e60085f05181/FederationMetadata/2007-06/FederationMetadata.xml
issuer: https://sts.windows.net/682febe8-021b-4fde-ac09-e60085f05181/
request_uri: https://login.microsoftonline.com/682febe8-021b-4fde-ac09-e60085f05181/saml2
public_key: '<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><X509Data><X509Certificate>MIIDBTCCAe2gAwIBAgIQev76BWqjWZxChmKkGqoAfDANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE4MDIxODAwMDAwMFoXDTIwMDIxOTAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMgmGiRfLh6Fdi99XI2VA3XKHStWNRLEy5Aw/gxFxchnh2kPdk/bejFOs2swcx7yUWqxujjCNRsLBcWfaKUlTnrkY7i9x9noZlMrijgJy/Lk+HH5HX24PQCDf+twjnHHxZ9G6/8VLM2e5ZBeZm+t7M3vhuumEHG3UwloLF6cUeuPdW+exnOB1U1fHBIFOG8ns4SSIoq6zw5rdt0CSI6+l7b1DEjVvPLtJF+zyjlJ1Qp7NgBvAwdiPiRMU4l8IRVbuSVKoKYJoyJ4L3eXsjczoBSTJ6VjV2mygz96DC70MY3avccFrk7tCEC6ZlMRBfY1XPLyldT7tsR3EuzjecSa1M8CAwEAAaMhMB8wHQYDVR0OBBYEFIks1srixjpSLXeiR8zES5cTY6fBMA0GCSqGSIb3DQEBCwUAA4IBAQCKthfK4C31DMuDyQZVS3F7+4Evld3hjiwqu2uGDK+qFZas/D/eDunxsFpiwqC01RIMFFN8yvmMjHphLHiBHWxcBTS+tm7AhmAvWMdxO5lzJLS+UWAyPF5ICROe8Mu9iNJiO5JlCo0Wpui9RbB1C81Xhax1gWHK245ESL6k7YWvyMYWrGqr1NuQcNS0B/AIT1Nsj1WY7efMJQOmnMHkPUTWryVZlthijYyd7P2Gz6rY5a81DAFqhDNJl2pGIAE6HWtSzeUEh3jCsHEkoglKfm4VrGJEuXcALmfCMbdfTvtu4rlsaP2hQad+MG/KJFlenoTK34EMHeBPDCpqNDz8UVNk</X509Certificate></X509Data></KeyInfo>'
requestUri: https://login.microsoftonline.com/682febe8-021b-4fde-ac09-e60085f05181/saml2
publicKey: '<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><X509Data><X509Certificate>MIIDBTCCAe2gAwIBAgIQev76BWqjWZxChmKkGqoAfDANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE4MDIxODAwMDAwMFoXDTIwMDIxOTAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMgmGiRfLh6Fdi99XI2VA3XKHStWNRLEy5Aw/gxFxchnh2kPdk/bejFOs2swcx7yUWqxujjCNRsLBcWfaKUlTnrkY7i9x9noZlMrijgJy/Lk+HH5HX24PQCDf+twjnHHxZ9G6/8VLM2e5ZBeZm+t7M3vhuumEHG3UwloLF6cUeuPdW+exnOB1U1fHBIFOG8ns4SSIoq6zw5rdt0CSI6+l7b1DEjVvPLtJF+zyjlJ1Qp7NgBvAwdiPiRMU4l8IRVbuSVKoKYJoyJ4L3eXsjczoBSTJ6VjV2mygz96DC70MY3avccFrk7tCEC6ZlMRBfY1XPLyldT7tsR3EuzjecSa1M8CAwEAAaMhMB8wHQYDVR0OBBYEFIks1srixjpSLXeiR8zES5cTY6fBMA0GCSqGSIb3DQEBCwUAA4IBAQCKthfK4C31DMuDyQZVS3F7+4Evld3hjiwqu2uGDK+qFZas/D/eDunxsFpiwqC01RIMFFN8yvmMjHphLHiBHWxcBTS+tm7AhmAvWMdxO5lzJLS+UWAyPF5ICROe8Mu9iNJiO5JlCo0Wpui9RbB1C81Xhax1gWHK245ESL6k7YWvyMYWrGqr1NuQcNS0B/AIT1Nsj1WY7efMJQOmnMHkPUTWryVZlthijYyd7P2Gz6rY5a81DAFqhDNJl2pGIAE6HWtSzeUEh3jCsHEkoglKfm4VrGJEuXcALmfCMbdfTvtu4rlsaP2hQad+MG/KJFlenoTK34EMHeBPDCpqNDz8UVNk</X509Certificate></X509Data></KeyInfo>'

# Used by spar-integration
# NB: this will run on http, without SSL. this "should" not be the
# case in production, but according to the standard it is technically
# legal.
mockIdp:
host: 127.0.0.1
port: 9001
3 changes: 3 additions & 0 deletions services/spar/package.yaml
Expand Up @@ -53,6 +53,7 @@ dependencies:
- wai-utilities
- warp
- x509
- xml-conduit
- yaml

library:
Expand Down Expand Up @@ -93,11 +94,13 @@ executables:
- test-integration
ghc-options: -threaded -rtsopts -with-rtsopts=-N
dependencies:
- async
- base64-bytestring
- galley-types
- hspec
- hspec-discover
- lens-aeson
- random
- spar
- wai
- xml-conduit
20 changes: 12 additions & 8 deletions services/spar/spar.integration.yaml
@@ -1,19 +1,23 @@
saml:
version: SAML2.0
log_level: Debug
logLevel: Debug

sp_host: 0.0.0.0
sp_port: 8088
sp_app_uri: https://app.wire.com/
sp_sso_uri: https://app.wire.com/sso
spHost: 0.0.0.0
spPort: 8088
spAppUri: https://app.wire.com/
spSsoUri: https://app.wire.com/sso

contacts:
- type: ContactBilling
company: evil corp.
given_name: Dr.
givenName: Dr.
surname: Girlfriend
email: email:president@evil.corp

spInfo:
metaURI: https://app.wire.com/sso/metadata # corresponds to 'APIMeta'
loginURI: https://app.wire.com/sso/initiate-login/ # corresponds to 'APIAuthReq' (without the idp id)

brig:
host: 127.0.0.1
port: 8082
Expand All @@ -27,7 +31,7 @@ cassandra:
# Wire/AWS specific, optional
# discoUrl: "https://"

maxttlAuthreq: 11
maxttlAuthresp: 11
maxttlAuthreq: 5
maxttlAuthresp: 5

logNetStrings: False # log using netstrings encoding (see http://cr.yp.to/proto/netstrings.txt)

0 comments on commit 0505f8f

Please sign in to comment.