Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e59d409
WPB-1906: Check if a user is verified when uploading assets
lepsa Sep 21, 2023
6f694f1
Merge remote-tracking branch 'origin/develop' into WPB-1906
lepsa Sep 21, 2023
b3450d9
Merge remote-tracking branch 'origin/develop' into WPB-1906
lepsa Sep 22, 2023
88d793d
Merge remote-tracking branch 'origin/develop' into WPB-1906
lepsa Sep 25, 2023
ced9d7a
WPB-1906: Trying to fix test errors, some of which may be from develop
lepsa Sep 25, 2023
dd43650
Merge remote-tracking branch 'origin/develop' into WPB-1906
lepsa Sep 27, 2023
89879d2
Merge remote-tracking branch 'origin/develop' into WPB-1906
lepsa Sep 28, 2023
0533ea9
Adding the changelog
lepsa Sep 28, 2023
20ccc85
WPB-1906: Removing changes to a test since upstream has fixed it.
lepsa Sep 28, 2023
2fbf174
WPB-1906: Adding a test for upload failure
lepsa Sep 28, 2023
8f1f895
WPB-1906: Updating more cargohold test config
lepsa Sep 29, 2023
370bf53
Merge remote-tracking branch 'origin/develop' into WPB-1906
lepsa Oct 11, 2023
ed50166
Update libs/wire-api/src/Wire/API/Error/Cargohold.hs
lepsa Oct 11, 2023
3f6b87a
WPB-1906: PR feedback
lepsa Oct 11, 2023
ed0f05f
Merge remote-tracking branch 'lepsa/WPB-1906' into WPB-1906
lepsa Oct 11, 2023
b45e22a
Merge remote-tracking branch 'origin/develop' into WPB-1906
lepsa Oct 12, 2023
1803dc5
WPB-1906: Adding an additional test for unknown users
lepsa Oct 12, 2023
998cf50
WPB-1906: Updates from PR comments
lepsa Oct 12, 2023
6b28938
Merge remote-tracking branch 'origin/develop' into WPB-1906
lepsa Oct 20, 2023
5ad4072
WPB-1906: Updating based on jira updates
lepsa Oct 20, 2023
9a03264
Merge remote-tracking branch 'origin/develop' into WPB-1906
akshaymankar Nov 7, 2023
bd92409
Add futurework
akshaymankar Nov 7, 2023
3a80b87
Better var names
akshaymankar Nov 7, 2023
5ff5838
wire-api: Expose servant based brig-internal client
akshaymankar Nov 7, 2023
ecfb3a0
carghold: Use brig internal client from wire-api
akshaymankar Nov 7, 2023
b7ca2ca
WPB-1906: Fixing small compile errors around imports
lepsa Nov 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1-api-changes/WPB-1906
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Un-verified users can no longer upload assets
4 changes: 4 additions & 0 deletions charts/cargohold/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ data:
port: 8080
{{- end }}

brig:
host: brig
port: 8080

aws:
{{- with .Values.config.aws }}
s3Bucket: {{ .s3Bucket }}
Expand Down
8 changes: 8 additions & 0 deletions charts/cargohold/templates/tests/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,11 @@ data:
cargohold:
host: cargohold
port: {{ .Values.service.internalPort }}
{{- if .Values.config.enableFederation }}
federator:
host: federator
port: 8080
{{- end }}
brig:
host: brig
port: 8080
1 change: 1 addition & 0 deletions integration/integration.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ library
SetupHelpers
Test.AccessUpdate
Test.AssetDownload
Test.AssetUpload
Test.B2B
Test.Brig
Test.Client
Expand Down
18 changes: 18 additions & 0 deletions integration/test/Test/AssetUpload.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module Test.AssetUpload where

import API.BrigInternal
import API.Cargohold
import SetupHelpers
import Testlib.Prelude

testAssetUploadUnverifiedUser :: HasCallStack => App ()
testAssetUploadUnverifiedUser = do
user <- randomUser OwnDomain $ def {activate = False}
bindResponse (uploadAsset user) $ \resp -> do
resp.status `shouldMatchInt` 403
Comment thread
akshaymankar marked this conversation as resolved.

testAssetUploadVerifiedUser :: HasCallStack => App ()
testAssetUploadVerifiedUser = do
user <- randomUser OwnDomain def
bindResponse (uploadAsset user) $ \resp -> do
resp.status `shouldMatchInt` 201
11 changes: 8 additions & 3 deletions libs/types-common/src/Data/CommaSeparatedList.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ module Data.CommaSeparatedList where

import Control.Lens ((?~))
import Data.Bifunctor qualified as Bifunctor
import Data.ByteString.Conversion (FromByteString, List, fromList, parser, runParser)
import Data.ByteString (toStrict)
import Data.ByteString.Conversion (FromByteString, List (..), ToByteString, builder, fromList, parser, runParser, toByteString)
import Data.OpenApi
import Data.Proxy (Proxy (..))
import Data.Range (Bounds, Range)
import Data.Text qualified as Text
import Data.Text.Encoding (encodeUtf8)
import Data.Text.Encoding (decodeUtf8With, encodeUtf8)
import Data.Text.Encoding.Error
import Imports
import Servant (FromHttpApiData (..))
import Servant (FromHttpApiData (..), ToHttpApiData (toQueryParam))

newtype CommaSeparatedList a = CommaSeparatedList {fromCommaSeparatedList :: [a]}
deriving stock (Show, Eq)
Expand All @@ -39,6 +41,9 @@ instance FromByteString (List a) => FromHttpApiData (CommaSeparatedList a) where
parseUrlPiece t =
CommaSeparatedList . fromList <$> Bifunctor.first Text.pack (runParser parser $ encodeUtf8 t)

instance ToByteString (List a) => ToHttpApiData (CommaSeparatedList a) where
toQueryParam (CommaSeparatedList l) = decodeUtf8With lenientDecode $ toStrict $ toByteString $ builder $ List l

instance ToParamSchema (CommaSeparatedList a) where
toParamSchema _ = mempty & type_ ?~ OpenApiString

Expand Down
6 changes: 6 additions & 0 deletions libs/wire-api/src/Wire/API/Error/Cargohold.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ data CargoholdError
| AssetTooLarge
| InvalidLength
| NoMatchingAssetEndpoint
| Unverified
Comment thread
akshaymankar marked this conversation as resolved.
Outdated
| UserNotFound

instance (Typeable (MapError e), KnownError (MapError e)) => IsSwaggerError (e :: CargoholdError) where
addToOpenApi = addStaticErrorToSwagger @(MapError e)
Expand All @@ -38,5 +40,9 @@ type instance MapError 'AssetTooLarge = 'StaticError 413 "client-error" "Asset t

type instance MapError 'InvalidLength = 'StaticError 400 "invalid-length" "Invalid content length"

type instance MapError 'Unverified = 'StaticError 403 "unverified-user" "Unverified user"

type instance MapError 'UserNotFound = 'StaticError 403 "not-found" "User not found"

-- | Return `AssetNotFound` to hide there's a multi-ingress setup.
type instance MapError 'NoMatchingAssetEndpoint = MapError 'AssetNotFound
2 changes: 1 addition & 1 deletion services/brig/src/Brig/API/Public.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1064,7 +1064,7 @@ deleteSelfUser ::
UserId ->
Public.DeleteUser ->
(Handler r) (Maybe Code.Timeout)
deleteSelfUser u body =
deleteSelfUser u body = do
API.deleteSelfUser u (Public.deleteUserPassword body) !>> deleteUserError

verifyDeleteUser :: Public.VerifyDeleteUser -> Handler r ()
Expand Down
7 changes: 5 additions & 2 deletions services/brig/test/integration/API/User/Account.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1454,7 +1454,10 @@ testDeleteAnonUser brig = do

testDeleteWithProfilePic :: Brig -> CargoHold -> Http ()
testDeleteWithProfilePic brig cargohold = do
uid <- userId <$> createAnonUser "anon" brig
-- A random local part, with nice, email-friendly characters
Comment thread
akshaymankar marked this conversation as resolved.
Outdated
email <- randomEmail
-- Users need to be verified if they want to upload assets, so email it is!
uid <- userId <$> createUserWithEmail "anon" email brig
ast <- responseJsonError =<< uploadAsset cargohold uid Asset.defAssetSettings "this is my profile pic"
-- Ensure that the asset is there
downloadAsset cargohold uid (ast ^. Asset.assetKey) !!! const 200 === statusCode
Expand All @@ -1469,7 +1472,7 @@ testDeleteWithProfilePic brig cargohold = do
-- Update profile with the uploaded asset
put (brig . path "/self" . contentJson . zUser uid . zConn "c" . body update)
!!! const 200 === statusCode
deleteUser uid Nothing brig !!! const 200 === statusCode
deleteUser uid (pure defPassword) brig !!! const 200 === statusCode
-- Check that the asset gets deleted
downloadAsset cargohold uid (ast ^. Asset.assetKey) !!! const 404 === statusCode

Expand Down
3 changes: 3 additions & 0 deletions services/cargohold/cargohold.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,12 @@ library
, resourcet >=1.1
, retry >=0.5
, servant
, servant-client
, servant-server
, text >=1.1
, time >=1.4
, tinylog >=0.10
, transformers
, transitive-anns
, types-common >=0.16
, types-common-aws
Expand Down Expand Up @@ -286,6 +288,7 @@ executable cargohold-integration
, mmorph
, mtl
, optparse-applicative
, safe
, servant-client
, tagged >=0.8
, tasty >=1.0
Expand Down
4 changes: 4 additions & 0 deletions services/cargohold/cargohold.integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ federator:
host: 127.0.0.1
port: 8097

brig:
host: 127.0.0.1
port: 8082

aws:
s3Bucket: dummy-bucket # <-- insert-bucket-name-here
s3Endpoint: http://localhost:4570 # https://s3-eu-west-1.amazonaws.com:443
Expand Down
5 changes: 5 additions & 0 deletions services/cargohold/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
, optparse-applicative
, resourcet
, retry
, safe
, servant
, servant-client
, servant-server
Expand All @@ -55,6 +56,7 @@
, text
, time
, tinylog
, transformers
, transitive-anns
, types-common
, types-common-aws
Expand Down Expand Up @@ -110,10 +112,12 @@ mkDerivation {
resourcet
retry
servant
servant-client
servant-server
text
time
tinylog
transformers
transitive-anns
types-common
types-common-aws
Expand Down Expand Up @@ -152,6 +156,7 @@ mkDerivation {
mmorph
mtl
optparse-applicative
safe
servant-client
tagged
tasty
Expand Down
6 changes: 6 additions & 0 deletions services/cargohold/src/CargoHold/API/Error.hs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ invalidLength = errorToWai @'InvalidLength
assetNotFound :: Error
assetNotFound = errorToWai @'AssetNotFound

unverified :: Error
unverified = errorToWai @'Unverified

userNotFound :: Error
userNotFound = errorToWai @'UserNotFound

invalidMD5 :: Error
invalidMD5 = mkError status400 "client-error" "Invalid MD5."

Expand Down
26 changes: 26 additions & 0 deletions services/cargohold/src/CargoHold/API/Public.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,36 @@

module CargoHold.API.Public (servantSitemap, internalSitemap) where

import CargoHold.API.Error (unverified, userNotFound)
import qualified CargoHold.API.Legacy as LegacyAPI
import CargoHold.API.Util
import qualified CargoHold.API.V3 as V3
import CargoHold.App
import CargoHold.Federation
import qualified CargoHold.Types.V3 as V3
import Control.Lens
import Control.Monad.Trans.Except (throwE)
import Data.ByteString.Builder
import qualified Data.ByteString.Lazy as LBS
import Data.CommaSeparatedList (CommaSeparatedList (..))
import Data.Domain
import Data.Id
import Data.Kind
import Data.Qualified
import Imports hiding (head)
import qualified Network.HTTP.Types as HTTP
import Servant.API
import Servant.Client
import Servant.Server hiding (Handler)
import URI.ByteString
import Wire.API.Asset
import Wire.API.Federation.API
import Wire.API.Routes.AssetBody
import qualified Wire.API.Routes.Internal.Brig as IBrig
import Wire.API.Routes.Internal.Cargohold
import Wire.API.Routes.Named (namedClient)
import Wire.API.Routes.Public.Cargohold
import Wire.API.User (accountUser, userIdentity)

servantSitemap :: ServerT CargoholdAPI Handler
servantSitemap =
Expand Down Expand Up @@ -136,9 +143,28 @@ uploadAssetV3 ::
AssetSource ->
Handler (Asset, AssetLocation Relative)
uploadAssetV3 pid req = do
clientEnv <- asks $ view brigClientEnv
let principal = mkPrincipal pid
getUser uid = runClientM (client' uid) clientEnv
case principal of
V3.UserPrincipal uid -> do
resp <- liftIO $ getUser uid
users <- either (const $ throwE userNotFound) pure resp
maybe
(throwE unverified)
(const (pure ()) . userIdentity . accountUser)
(listToMaybe users)
Comment thread
akshaymankar marked this conversation as resolved.
Outdated
_ -> pure ()
asset <- V3.upload principal (getAssetSource req)
pure (fmap tUntagged asset, mkAssetLocation @tag (asset ^. assetKey))
where
client' uid =
Comment thread
akshaymankar marked this conversation as resolved.
Outdated
namedClient @IBrig.API @"iGetUsersByVariousKeys"
(pure $ CommaSeparatedList [uid])
Nothing
Nothing
Nothing
Nothing

downloadAssetV3 ::
MakePrincipal tag id =>
Expand Down
15 changes: 11 additions & 4 deletions services/cargohold/src/CargoHold/App.hs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ module CargoHold.App
localUnit,
options,
settings,
brigClientEnv,

-- * App Monad
AppT,
Expand All @@ -53,7 +54,7 @@ import Bilge (Manager, MonadHttp, RequestId (..), newManager, withResponse)
import qualified Bilge
import Bilge.RPC (HasRequestId (..))
import qualified CargoHold.AWS as AWS
import CargoHold.Options (AWSOpts, Opts, S3Compatibility (..))
import CargoHold.Options (AWSOpts, Opts, S3Compatibility (..), brig)
import qualified CargoHold.Options as Opt
import Control.Error (ExceptT, exceptT)
import Control.Exception (throw)
Expand All @@ -65,13 +66,15 @@ import qualified Data.Map as Map
import Data.Metrics.Middleware (Metrics)
import qualified Data.Metrics.Middleware as Metrics
import Data.Qualified
import qualified Data.Text as T
import HTTP2.Client.Manager (Http2Manager, http2ManagerWithSSLCtx)
import Imports hiding (log)
import Network.HTTP.Client (ManagerSettings (..), requestHeaders, responseTimeoutMicro)
import Network.HTTP.Client (ManagerSettings (..), defaultManagerSettings, requestHeaders, responseTimeoutMicro)
import Network.HTTP.Client.OpenSSL
import Network.Wai.Utilities (Error (..))
import OpenSSL.Session (SSLContext, SSLOption (..))
import qualified OpenSSL.Session as SSL
import Servant.Client
import System.Logger.Class hiding (settings)
import qualified System.Logger.Extended as Log
import Util.Options
Expand All @@ -88,7 +91,8 @@ data Env = Env
_requestId :: RequestId,
_options :: Opt.Opts,
_localUnit :: Local (),
_multiIngress :: Map String AWS.Env
_multiIngress :: Map String AWS.Env,
_brigClientEnv :: ClientEnv
}

makeLenses ''Env
Expand All @@ -106,7 +110,10 @@ newEnv o = do
ama <- initAws (o ^. Opt.aws) lgr mgr
multiIngressAWS <- initMultiIngressAWS lgr mgr
let loc = toLocalUnsafe (o ^. Opt.settings . Opt.federationDomain) ()
pure $ Env ama met lgr mgr h2mgr def o loc multiIngressAWS
(Endpoint h p) = o ^. brig
baseUrl = BaseUrl Http (T.unpack h) (fromIntegral p) ""
clientEnv <- liftIO $ newManager defaultManagerSettings <&> \m -> ClientEnv m baseUrl Nothing defaultMakeClientRequest
Comment thread
akshaymankar marked this conversation as resolved.
Outdated
pure $ Env ama met lgr mgr h2mgr def o loc multiIngressAWS clientEnv
Comment thread
akshaymankar marked this conversation as resolved.
Outdated
where
initMultiIngressAWS :: Logger -> Manager -> IO (Map String AWS.Env)
initMultiIngressAWS lgr mgr =
Expand Down
2 changes: 2 additions & 0 deletions services/cargohold/src/CargoHold/Options.hs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ data Opts = Opts
_settings :: !Settings,
-- | Federator endpoint
_federator :: Maybe Endpoint,
-- | Brig endpoint
_brig :: !Endpoint,
-- Logging

-- | Log level (Debug, Info, etc)
Expand Down
18 changes: 9 additions & 9 deletions services/cargohold/src/CargoHold/Run.hs
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,16 @@ mkApp o = Codensity $ \k ->
. GZip.gzip GZip.def
. catchErrors (e ^. appLogger) [Right $ e ^. metrics]
servantApp :: Env -> Application
servantApp e0 r =
servantApp e0 r = do
let e = set requestId (maybe def RequestId (lookupRequestId r)) e0
in Servant.serveWithContext
(Proxy @CombinedAPI)
((o ^. settings . federationDomain) :. Servant.EmptyContext)
( hoistServerWithDomain @FederationAPI (toServantHandler e) federationSitemap
:<|> hoistServerWithDomain @CargoholdAPI (toServantHandler e) servantSitemap
:<|> hoistServerWithDomain @InternalAPI (toServantHandler e) internalSitemap
)
r
Servant.serveWithContext
(Proxy @CombinedAPI)
((o ^. settings . federationDomain) :. Servant.EmptyContext)
( hoistServerWithDomain @FederationAPI (toServantHandler e) federationSitemap
:<|> hoistServerWithDomain @CargoholdAPI (toServantHandler e) servantSitemap
:<|> hoistServerWithDomain @InternalAPI (toServantHandler e) internalSitemap
)
r

toServantHandler :: Env -> Handler a -> Servant.Handler a
toServantHandler env = liftIO . runHandler env
Expand Down
Loading