diff --git a/cassandra-schema.cql b/cassandra-schema.cql index e9e0b6c8b9..9665f03554 100644 --- a/cassandra-schema.cql +++ b/cassandra-schema.cql @@ -741,6 +741,24 @@ CREATE TABLE brig_test.team_invitation_info ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; +CREATE TABLE brig_test.provider_keys ( + key text PRIMARY KEY, + provider uuid +) WITH bloom_filter_fp_chance = 0.1 + AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND comment = '' + AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} + AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + CREATE TABLE brig_test.rich_info ( user uuid PRIMARY KEY, json blob @@ -801,12 +819,14 @@ CREATE TABLE brig_test.service_tag ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE brig_test.login_codes ( - user uuid PRIMARY KEY, - code text, - retries int, - timeout timestamp -) WITH bloom_filter_fp_chance = 0.01 +CREATE TABLE brig_test.meta ( + id int, + version int, + date timestamp, + descr text, + PRIMARY KEY (id, version) +) WITH CLUSTERING ORDER BY (version ASC) + AND bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} @@ -1160,13 +1180,35 @@ CREATE TABLE brig_test.nonce ( AND read_repair_chance = 0.0 AND speculative_retry = '99PERCENTILE'; -CREATE TABLE brig_test.provider_keys ( - key text PRIMARY KEY, - provider uuid -) WITH bloom_filter_fp_chance = 0.1 +CREATE TABLE brig_test.login_codes ( + user uuid PRIMARY KEY, + code text, + retries int, + timeout timestamp +) WITH bloom_filter_fp_chance = 0.01 AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} AND comment = '' - AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'} + AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} + AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} + AND crc_check_chance = 1.0 + AND dclocal_read_repair_chance = 0.1 + AND default_time_to_live = 0 + AND gc_grace_seconds = 864000 + AND max_index_interval = 2048 + AND memtable_flush_period_in_ms = 0 + AND min_index_interval = 128 + AND read_repair_chance = 0.0 + AND speculative_retry = '99PERCENTILE'; + +CREATE TABLE brig_test.oauth_client ( + id uuid PRIMARY KEY, + name text, + redirect_uri blob, + secret blob +) WITH bloom_filter_fp_chance = 0.01 + AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} + AND comment = '' + AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} AND crc_check_chance = 1.0 AND dclocal_read_repair_chance = 0.1 @@ -1529,28 +1571,6 @@ CREATE TABLE brig_test.connection ( AND speculative_retry = '99PERCENTILE'; CREATE INDEX conn_status ON brig_test.connection (status); -CREATE TABLE brig_test.meta ( - id int, - version int, - date timestamp, - descr text, - PRIMARY KEY (id, version) -) WITH CLUSTERING ORDER BY (version ASC) - AND bloom_filter_fp_chance = 0.01 - AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'} - AND comment = '' - AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'} - AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} - AND crc_check_chance = 1.0 - AND dclocal_read_repair_chance = 0.1 - AND default_time_to_live = 0 - AND gc_grace_seconds = 864000 - AND max_index_interval = 2048 - AND memtable_flush_period_in_ms = 0 - AND min_index_interval = 128 - AND read_repair_chance = 0.0 - AND speculative_retry = '99PERCENTILE'; - CREATE TABLE brig_test.invitation ( inviter uuid, id uuid, diff --git a/changelog.d/5-internal/pr-2882 b/changelog.d/5-internal/pr-2882 new file mode 100644 index 0000000000..102110ca95 --- /dev/null +++ b/changelog.d/5-internal/pr-2882 @@ -0,0 +1 @@ +New internal endpoint to register OAuth clients diff --git a/charts/nginz/values.yaml b/charts/nginz/values.yaml index 3ff3fbe435..afbfce903c 100644 --- a/charts/nginz/values.yaml +++ b/charts/nginz/values.yaml @@ -377,6 +377,14 @@ nginx_conf: envs: - all disable_zauth: true + - path: /oauth/clients/([^/]*)$ + envs: + - all + - path: i/oauth/clients$ + envs: + - staging + disable_zauth: true + basic_auth: true galley: - path: /conversations/code-check disable_zauth: true diff --git a/libs/types-common/src/Data/Id.hs b/libs/types-common/src/Data/Id.hs index bcdd57aa12..245e123a3f 100644 --- a/libs/types-common/src/Data/Id.hs +++ b/libs/types-common/src/Data/Id.hs @@ -49,6 +49,7 @@ module Data.Id RequestId (..), BotId (..), NoId, + OAuthClientId, ) where @@ -87,7 +88,7 @@ import Servant (FromHttpApiData (..), ToHttpApiData (..)) import Test.QuickCheck import Test.QuickCheck.Instances () -data IdTag = A | C | I | U | P | S | T | STo +data IdTag = A | C | I | U | P | S | T | STo | OAuthClient idTagName :: IdTag -> Text idTagName A = "Asset" @@ -98,6 +99,7 @@ idTagName P = "Provider" idTagName S = "Service" idTagName T = "Team" idTagName STo = "ScimToken" +idTagName OAuthClient = "OAuthClient" class KnownIdTag (t :: IdTag) where idTagValue :: IdTag @@ -118,6 +120,8 @@ instance KnownIdTag 'T where idTagValue = T instance KnownIdTag 'STo where idTagValue = STo +instance KnownIdTag 'OAuthClient where idTagValue = OAuthClient + type AssetId = Id 'A type InvitationId = Id 'I @@ -136,6 +140,8 @@ type TeamId = Id 'T type ScimTokenId = Id 'STo +type OAuthClientId = Id 'OAuthClient + -- Id ------------------------------------------------------------------------- data NoId = NoId deriving (Eq, Show, Generic) diff --git a/services/brig/brig.cabal b/services/brig/brig.cabal index 09e7f1aa14..426c48efab 100644 --- a/services/brig/brig.cabal +++ b/services/brig/brig.cabal @@ -30,6 +30,7 @@ library Brig.API.MLS.KeyPackages Brig.API.MLS.KeyPackages.Validation Brig.API.MLS.Util + Brig.API.OAuth Brig.API.Properties Brig.API.Public Brig.API.Public.Swagger @@ -442,6 +443,7 @@ executable brig-integration API.Metrics API.MLS API.MLS.Util + API.OAuth API.Provider API.RichInfo.Util API.Search @@ -680,6 +682,7 @@ executable brig-schema V71_AddTableVCodesThrottle V72_AddNonceTable V73_ReplaceNonceTable + V74_AddOAuthClientTable V9 V_FUTUREWORK diff --git a/services/brig/schema/src/Main.hs b/services/brig/schema/src/Main.hs index db7ca577b4..94ad8981bb 100644 --- a/services/brig/schema/src/Main.hs +++ b/services/brig/schema/src/Main.hs @@ -83,6 +83,7 @@ import qualified V70_UserEmailUnvalidated import qualified V71_AddTableVCodesThrottle import qualified V72_AddNonceTable import qualified V73_ReplaceNonceTable +import qualified V74_AddOAuthClientTable import qualified V9 main :: IO () @@ -155,7 +156,8 @@ main = do V70_UserEmailUnvalidated.migration, V71_AddTableVCodesThrottle.migration, V72_AddNonceTable.migration, - V73_ReplaceNonceTable.migration + V73_ReplaceNonceTable.migration, + V74_AddOAuthClientTable.migration -- When adding migrations here, don't forget to update -- 'schemaVersion' in Brig.App diff --git a/services/brig/schema/src/V74_AddOAuthClientTable.hs b/services/brig/schema/src/V74_AddOAuthClientTable.hs new file mode 100644 index 0000000000..989797f260 --- /dev/null +++ b/services/brig/schema/src/V74_AddOAuthClientTable.hs @@ -0,0 +1,40 @@ +{-# LANGUAGE QuasiQuotes #-} + +-- This file is part of the Wire Server implementation. +-- +-- Copyright (C) 2022 Wire Swiss GmbH +-- +-- This program is free software: you can redistribute it and/or modify it under +-- the terms of the GNU Affero General Public License as published by the Free +-- Software Foundation, either version 3 of the License, or (at your option) any +-- later version. +-- +-- This program is distributed in the hope that it will be useful, but WITHOUT +-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +-- details. +-- +-- You should have received a copy of the GNU Affero General Public License along +-- with this program. If not, see . + +module V74_AddOAuthClientTable + ( migration, + ) +where + +import Cassandra.Schema +import Imports +import Text.RawString.QQ + +migration :: Migration +migration = + Migration 74 "Add table for OAuth clients" $ do + schema' + [r| + CREATE TABLE IF NOT EXISTS oauth_client + ( id uuid PRIMARY KEY + , name text + , redirect_uri blob + , secret blob + ) + |] diff --git a/services/brig/src/Brig/API/Internal.hs b/services/brig/src/Brig/API/Internal.hs index bb0c7f75bc..9051b52fe2 100644 --- a/services/brig/src/Brig/API/Internal.hs +++ b/services/brig/src/Brig/API/Internal.hs @@ -30,6 +30,7 @@ import qualified Brig.API.Connection as API import Brig.API.Error import Brig.API.Handler import Brig.API.MLS.KeyPackages.Validation +import Brig.API.OAuth (IOAuthAPI, internalOauthAPI) import Brig.API.Types import qualified Brig.API.User as API import qualified Brig.API.User as Api @@ -105,6 +106,16 @@ import Wire.API.User.RichInfo -- Sitemap (servant) servantSitemap :: + Members + '[ BlacklistStore, + GalleyProvider, + UserPendingActivationStore p + ] + r => + ServerT (BrigIRoutes.API :<|> IOAuthAPI) (Handler r) +servantSitemap = brigInternalAPI :<|> internalOauthAPI + +brigInternalAPI :: Members '[ BlacklistStore, GalleyProvider, @@ -112,7 +123,7 @@ servantSitemap :: ] r => ServerT BrigIRoutes.API (Handler r) -servantSitemap = +brigInternalAPI = ejpdAPI :<|> accountAPI :<|> mlsAPI diff --git a/services/brig/src/Brig/API/OAuth.hs b/services/brig/src/Brig/API/OAuth.hs new file mode 100644 index 0000000000..f05bebbed5 --- /dev/null +++ b/services/brig/src/Brig/API/OAuth.hs @@ -0,0 +1,218 @@ +-- This file is part of the Wire Server implementation. +-- +-- Copyright (C) 2022 Wire Swiss GmbH +-- +-- This program is free software: you can redistribute it and/or modify it under +-- the terms of the GNU Affero General Public License as published by the Free +-- Software Foundation, either version 3 of the License, or (at your option) any +-- later version. +-- +-- This program is distributed in the hope that it will be useful, but WITHOUT +-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +-- details. +-- +-- You should have received a copy of the GNU Affero General Public License along +-- with this program. If not, see . + +module Brig.API.OAuth where + +import Brig.API.Handler (Handler) +import Brig.App (Env, wrapClient) +import Brig.Password (Password, mkSafePassword) +import Cassandra +import Control.Monad.Except +import qualified Data.Aeson as A +import Data.ByteString.Conversion +import Data.ByteString.Lazy (toStrict) +import Data.Id (OAuthClientId, UserId, randomId) +import Data.Misc (PlainTextPassword (PlainTextPassword)) +import Data.Range +import Data.Schema +import qualified Data.Swagger as S +import Data.Text.Ascii +import qualified Data.Text.Encoding as TE +import Imports +import OpenSSL.Random (randBytes) +import Servant hiding (Handler, Tagged) +import URI.ByteString +import Wire.API.Error +import Wire.API.Routes.MultiVerb +import Wire.API.Routes.Named (Named (..)) +import Wire.API.Routes.Public (ZUser) + +-------------------------------------------------------------------------------- +-- Types + +newtype RedirectUrl = RedirectUrl {unRedirectUrl :: URIRef Absolute} + deriving stock (Eq, Show, Generic) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema RedirectUrl) + +instance ToByteString RedirectUrl where + builder = serializeURIRef . unRedirectUrl + +instance FromByteString RedirectUrl where + parser = RedirectUrl <$> uriParser strictURIParserOptions + +instance ToSchema RedirectUrl where + schema = + (TE.decodeUtf8 . serializeURIRef' . unRedirectUrl) + .= (RedirectUrl <$> parsedText "RedirectUrl" (runParser (uriParser strictURIParserOptions) . TE.encodeUtf8)) + +newtype OAuthApplicationName = OAuthApplicationName {unOAuthApplicationName :: Range 1 256 Text} + deriving stock (Eq, Show, Generic) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthApplicationName) + +instance ToSchema OAuthApplicationName where + schema = OAuthApplicationName <$> unOAuthApplicationName .= schema + +data NewOAuthClient = NewOAuthClient + { nocApplicationName :: OAuthApplicationName, + nocRedirectUrl :: RedirectUrl + } + deriving stock (Eq, Show, Generic) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema NewOAuthClient) + +instance ToSchema NewOAuthClient where + schema = + object "NewOAuthClient" $ + NewOAuthClient + <$> nocApplicationName .= field "applicationName" schema + <*> nocRedirectUrl .= field "redirectUrl" schema + +newtype OAuthClientPlainTextSecret = OAuthClientPlainTextSecret {unOAuthClientPlainTextSecret :: AsciiBase16} + deriving stock (Eq, Generic) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthClientPlainTextSecret) + +instance Show OAuthClientPlainTextSecret where + show _ = "" + +instance ToSchema OAuthClientPlainTextSecret where + schema = (toText . unOAuthClientPlainTextSecret) .= parsedText "OAuthClientPlainTextSecret" (fmap OAuthClientPlainTextSecret . validateBase16) + +data OAuthClientCredentials = OAuthClientCredentials + { occClientId :: OAuthClientId, + occClientSecret :: OAuthClientPlainTextSecret + } + deriving stock (Eq, Show, Generic) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthClientCredentials) + +instance ToSchema OAuthClientCredentials where + schema = + object "OAuthClientCredentials" $ + OAuthClientCredentials + <$> occClientId .= field "clientId" schema + <*> occClientSecret .= field "clientSecret" schema + +data OAuthClient = OAuthClient + { ocId :: OAuthClientId, + ocName :: OAuthApplicationName, + ocRedirectUrl :: RedirectUrl + } + deriving stock (Eq, Show, Generic) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via (Schema OAuthClient) + +instance ToSchema OAuthClient where + schema = + object "OAuthClient" $ + OAuthClient + <$> ocId .= field "clientId" schema + <*> ocName .= field "applicationName" schema + <*> ocRedirectUrl .= field "redirectUrl" schema + +-------------------------------------------------------------------------------- +-- API Internal + +type IOAuthAPI = + Named + "create-oauth-client" + ( Summary "Register an OAuth client" + :> "i" + :> "oauth" + :> "clients" + :> ReqBody '[JSON] NewOAuthClient + :> Post '[JSON] OAuthClientCredentials + ) + +internalOauthAPI :: ServerT IOAuthAPI (Handler r) +internalOauthAPI = + Named @"create-oauth-client" createNewOAuthClient + +-------------------------------------------------------------------------------- +-- API Public + +data OAuthError = OAuthClientNotFound + +type instance MapError 'OAuthClientNotFound = 'StaticError 404 "not-found" "OAuth client not found" + +type OAuthAPI = + Named + "get-oauth-client" + ( Summary "Get OAuth client information" + :> ZUser + :> "oauth" + :> "clients" + :> Capture "ClientId" OAuthClientId + :> MultiVerb + 'GET + '[JSON] + '[ ErrorResponse 'OAuthClientNotFound, + Respond 200 "OAuth client found" OAuthClient + ] + (Maybe OAuthClient) + ) + +oauthAPI :: ServerT OAuthAPI (Handler r) +oauthAPI = + Named @"get-oauth-client" getOAuthClient + +-------------------------------------------------------------------------------- +-- Handlers + +createNewOAuthClient :: NewOAuthClient -> (Handler r) OAuthClientCredentials +createNewOAuthClient (NewOAuthClient name uri) = do + credentials@(OAuthClientCredentials cid secret) <- OAuthClientCredentials <$> randomId <*> createSecret + safeSecret <- liftIO $ hashClientSecret secret + lift $ wrapClient $ insertOAuthClient cid name uri safeSecret + pure credentials + where + createSecret :: MonadIO m => m OAuthClientPlainTextSecret + createSecret = liftIO . fmap (OAuthClientPlainTextSecret . encodeBase16) $ randBytes 32 + + hashClientSecret :: MonadIO m => OAuthClientPlainTextSecret -> m Password + hashClientSecret = mkSafePassword . PlainTextPassword . toText . unOAuthClientPlainTextSecret + +getOAuthClient :: UserId -> OAuthClientId -> (Handler r) (Maybe OAuthClient) +getOAuthClient _ cid = lift $ wrapClient $ lookupOauthClient cid + +-------------------------------------------------------------------------------- +-- DB + +insertOAuthClient :: (MonadClient m, MonadReader Env m) => OAuthClientId -> OAuthApplicationName -> RedirectUrl -> Password -> m () +insertOAuthClient cid name uri pw = retry x5 . write q $ params LocalQuorum (cid, name, uri, pw) + where + q :: PrepQuery W (OAuthClientId, OAuthApplicationName, RedirectUrl, Password) () + q = "INSERT INTO oauth_client (id, name, redirect_uri, secret) VALUES (?, ?, ?, ?)" + +lookupOauthClient :: (MonadClient m, MonadReader Env m) => OAuthClientId -> m (Maybe OAuthClient) +lookupOauthClient cid = do + mNameUrl <- retry x5 . query1 q $ params LocalQuorum (Identity cid) + pure $ mNameUrl <&> uncurry (OAuthClient cid) + where + q :: PrepQuery R (Identity OAuthClientId) (OAuthApplicationName, RedirectUrl) + q = "SELECT name, redirect_uri FROM oauth_client WHERE id = ?" + +-------------------------------------------------------------------------------- +-- CQL instances + +instance Cql OAuthApplicationName where + ctype = Tagged TextColumn + toCql = CqlText . fromRange . unOAuthApplicationName + fromCql (CqlText t) = checkedEither t <&> OAuthApplicationName + fromCql _ = Left "OAuthApplicationName: Text expected" + +instance Cql RedirectUrl where + ctype = Tagged BlobColumn + toCql = CqlBlob . toByteString + fromCql (CqlBlob t) = runParser parser (toStrict t) + fromCql _ = Left "RedirectUrl: Blob expected" diff --git a/services/brig/src/Brig/API/Public.hs b/services/brig/src/Brig/API/Public.hs index becf2a175d..c1f0376505 100644 --- a/services/brig/src/Brig/API/Public.hs +++ b/services/brig/src/Brig/API/Public.hs @@ -33,6 +33,7 @@ import qualified Brig.API.Connection as API import Brig.API.Error import Brig.API.Handler import Brig.API.MLS.KeyPackages +import Brig.API.OAuth (OAuthAPI, oauthAPI) import qualified Brig.API.Properties as API import Brig.API.Public.Swagger import Brig.API.Types @@ -182,24 +183,26 @@ servantSitemap :: UserPendingActivationStore p ] r => - ServerT BrigAPI (Handler r) -servantSitemap = - userAPI - :<|> selfAPI - :<|> accountAPI - :<|> clientAPI - :<|> prekeyAPI - :<|> userClientAPI - :<|> connectionAPI - :<|> propertiesAPI - :<|> mlsAPI - :<|> userHandleAPI - :<|> searchAPI - :<|> authAPI - :<|> callingAPI - :<|> Team.servantAPI - :<|> systemSettingsAPI + ServerT (BrigAPI :<|> OAuthAPI) (Handler r) +servantSitemap = brigAPI :<|> oauthAPI where + brigAPI :: ServerT BrigAPI (Handler r) + brigAPI = + userAPI + :<|> selfAPI + :<|> accountAPI + :<|> clientAPI + :<|> prekeyAPI + :<|> userClientAPI + :<|> connectionAPI + :<|> propertiesAPI + :<|> mlsAPI + :<|> userHandleAPI + :<|> searchAPI + :<|> authAPI + :<|> callingAPI + :<|> Team.servantAPI + :<|> systemSettingsAPI userAPI :: ServerT UserAPI (Handler r) userAPI = Named @"get-user-unqualified" getUserUnqualifiedH diff --git a/services/brig/src/Brig/App.hs b/services/brig/src/Brig/App.hs index fb600c55cb..477c4dad39 100644 --- a/services/brig/src/Brig/App.hs +++ b/services/brig/src/Brig/App.hs @@ -149,7 +149,7 @@ import Util.Options import Wire.API.User schemaVersion :: Int32 -schemaVersion = 73 +schemaVersion = 74 ------------------------------------------------------------------------------- -- Environment diff --git a/services/brig/src/Brig/Run.hs b/services/brig/src/Brig/Run.hs index 66c8a5fda0..6656a22816 100644 --- a/services/brig/src/Brig/Run.hs +++ b/services/brig/src/Brig/Run.hs @@ -28,6 +28,7 @@ import Brig.API (sitemap) import Brig.API.Federation import Brig.API.Handler import qualified Brig.API.Internal as IAPI +import Brig.API.OAuth (IOAuthAPI, OAuthAPI) import Brig.API.Public (DocsAPI, docsAPI, servantSitemap) import qualified Brig.API.User as API import Brig.AWS (amazonkaEnv, sesQueue) @@ -140,8 +141,8 @@ mkApp o = do (Proxy @ServantCombinedAPI) (customFormatters :. localDomain :. Servant.EmptyContext) ( docsAPI - :<|> hoistServerWithDomain @BrigAPI (toServantHandler e) servantSitemap - :<|> hoistServerWithDomain @IAPI.API (toServantHandler e) IAPI.servantSitemap + :<|> hoistServerWithDomain @(BrigAPI :<|> OAuthAPI) (toServantHandler e) servantSitemap + :<|> hoistServerWithDomain @(IAPI.API :<|> IOAuthAPI) (toServantHandler e) IAPI.servantSitemap :<|> hoistServerWithDomain @FederationAPI (toServantHandler e) federationSitemap :<|> hoistServerWithDomain @VersionAPI (toServantHandler e) versionAPI :<|> Servant.Tagged (app e) @@ -149,8 +150,8 @@ mkApp o = do type ServantCombinedAPI = ( DocsAPI - :<|> BrigAPI - :<|> IAPI.API + :<|> (BrigAPI :<|> OAuthAPI) + :<|> (IAPI.API :<|> IOAuthAPI) :<|> FederationAPI :<|> VersionAPI :<|> Servant.Raw diff --git a/services/brig/test/integration/API/OAuth.hs b/services/brig/test/integration/API/OAuth.hs new file mode 100644 index 0000000000..06b65c0510 --- /dev/null +++ b/services/brig/test/integration/API/OAuth.hs @@ -0,0 +1,62 @@ +{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} + +-- This file is part of the Wire Server implementation. +-- +-- Copyright (C) 2022 Wire Swiss GmbH +-- +-- This program is free software: you can redistribute it and/or modify it under +-- the terms of the GNU Affero General Public License as published by the Free +-- Software Foundation, either version 3 of the License, or (at your option) any +-- later version. +-- +-- This program is distributed in the hope that it will be useful, but WITHOUT +-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +-- details. +-- +-- You should have received a copy of the GNU Affero General Public License along +-- with this program. If not, see . + +module API.OAuth where + +import Bilge +import Bilge.Assert +import Brig.API.OAuth +import Brig.Options +import Data.ByteString.Conversion (toByteString') +import Data.Id (OAuthClientId, UserId) +import Data.Range (unsafeRange) +import Imports +import Test.Tasty +import Test.Tasty.HUnit +import URI.ByteString (parseURI, strictURIParserOptions) +import Util +import Wire.API.User + +tests :: Manager -> Brig -> Opts -> TestTree +tests m b _opts = do + testGroup "oauth" $ + [test m "register new OAuth client" $ testRegisterNewOAuthClient b] + +testRegisterNewOAuthClient :: Brig -> Http () +testRegisterNewOAuthClient brig = do + let Right redirectUrl = RedirectUrl <$> parseURI strictURIParserOptions "https://example.com" + let applicationName = OAuthApplicationName (unsafeRange "E Corp") + let newOAuthClient = NewOAuthClient applicationName redirectUrl + cid <- occClientId <$> registerNewOAuthClient brig newOAuthClient + uid <- userId <$> randomUser brig + oauthClientInfo <- getOAuthClientInfo brig uid cid + liftIO $ do + applicationName @?= ocName oauthClientInfo + redirectUrl @?= ocRedirectUrl oauthClientInfo + +------------------------------------------------------------------------------- +-- Util + +registerNewOAuthClient :: HasCallStack => Brig -> NewOAuthClient -> Http OAuthClientCredentials +registerNewOAuthClient brig reqBody = + responseJsonError =<< post (brig . paths ["i", "oauth", "clients"] . json reqBody) Brig -> UserId -> OAuthClientId -> Http OAuthClient +getOAuthClientInfo brig uid cid = + responseJsonError =<< get (brig . paths ["oauth", "clients", toByteString' cid] . zUser uid) [federationEnd2End | includeFederationTests] where diff --git a/services/nginz/integration-test/conf/nginz/nginx.conf b/services/nginz/integration-test/conf/nginz/nginx.conf index e1d5f3fb6c..96d829fe63 100644 --- a/services/nginz/integration-test/conf/nginz/nginx.conf +++ b/services/nginz/integration-test/conf/nginz/nginx.conf @@ -291,6 +291,11 @@ http { proxy_pass http://brig; } + location ~* ^/oauth/clients/([^/]*)$ { + include common_response_with_zauth.conf; + proxy_pass http://brig; + } + # Cargohold Endpoints rewrite ^/api-docs/assets /assets/api-docs?base_url=http://127.0.0.1:8080/ break;