Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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/token-client-id
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The `/access` endpoint now takes an optional `client_id` query parameter. The first time it is provided, a new user token will be generated containing the given client ID. Successive invocations of `/access` will ignore the `client_id` parameter. Some endpoints can now potentially require a client ID as part of the access token. When trying to invoke them with an access token that does not contain a client ID, an authentication error will occur.
1 change: 1 addition & 0 deletions charts/cannon/templates/conf/_nginx.conf.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ http {

proxy_set_header Z-Type $zauth_type;
proxy_set_header Z-User $zauth_user;
proxy_set_header Z-Client $zauth_client;
proxy_set_header Z-Connection $zauth_connection;
proxy_set_header Z-Provider $zauth_provider;
proxy_set_header Z-Bot $zauth_bot;
Expand Down
1 change: 1 addition & 0 deletions charts/nginz/templates/conf/_nginx.conf.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ http {

proxy_set_header Z-Type $zauth_type;
proxy_set_header Z-User $zauth_user;
proxy_set_header Z-Client $zauth_client;
proxy_set_header Z-Connection $zauth_connection;
proxy_set_header Z-Provider $zauth_provider;
proxy_set_header Z-Bot $zauth_bot;
Expand Down
1 change: 1 addition & 0 deletions deploy/services-demo/conf/nginz/common_response.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
proxy_set_header Connection "";
proxy_set_header Z-Type $zauth_type;
proxy_set_header Z-User $zauth_user;
proxy_set_header Z-Client $zauth_client;
proxy_set_header Z-Connection $zauth_connection;
proxy_set_header Z-Provider $zauth_provider;
proxy_set_header Z-Bot $zauth_bot;
Expand Down
1 change: 1 addition & 0 deletions libs/libzauth/libzauth/rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
disable_all_formatting = true
34 changes: 34 additions & 0 deletions libs/libzauth/libzauth/src/zauth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,15 @@ mod tests {
const ACCESS_TOKEN: &'static str =
"aEPOxMwUriGEv2qc7Pb672ygy-6VeJ-8VrX3jmwalZr7xygU4izyCWxiT7IXfybnNGIsk1FQPb0RRVPx1s2UCw==.v=1.k=1.d=1466770783.t=a.l=.u=6562d941-4f40-4db4-b96e-56a06d71c2c3.c=11019722839397809329";

const ACCESS_TOKEN_CLIENT_ID: &'static str =
"aEPOxMwUriGEv2qc7Pb672ygy-6VeJ-8VrX3jmwalZr7xygU4izyCWxiT7IXfybnNGIsk1FQPb0RRVPx1s2UCw==.v=1.k=1.d=1466770783.t=a.l=.u=6562d941-4f40-4db4-b96e-56a06d71c2c3.c=11019722839397809329.i=deadbeef";

const USER_TOKEN: &'static str =
"vpJs7PEgwtsuzGlMY0-Vqs22s8o9ZDlp7wJrPmhCgIfg0NoTAxvxq5OtknabLMfNTEW9amn5tyeUM7tbFZABBA==.v=1.k=1.d=1466770905.t=u.l=.u=6562d941-4f40-4db4-b96e-56a06d71c2c3.r=4feacc";

const USER_TOKEN_CLIENT_ID: &'static str =
"vpJs7PEgwtsuzGlMY0-Vqs22s8o9ZDlp7wJrPmhCgIfg0NoTAxvxq5OtknabLMfNTEW9amn5tyeUM7tbFZABBA==.v=1.k=1.d=1466770905.t=u.l=.u=6562d941-4f40-4db4-b96e-56a06d71c2c3.r=4feacc.i=deadbeef";

const BOT_TOKEN: &'static str =
"-cEsTNb68hb-By81MZd5fF6NMDVzR_emkV_HfOnIdZTXsoeRRRZA7hmv9y2uLUNWDifNd-B8u0AjiAT_2rzUDg==.v=1.k=1.d=-1.t=b.l=.p=cd57deb3-bab6-46fd-be28-a3d48ef2c6b7.b=b46833f9-ec2a-4c4a-8304-1a367f849467.c=ae3d1b9e-e47c-4e10-a751-e99a64ada74b";

Expand Down Expand Up @@ -245,6 +251,20 @@ mod tests {
assert_eq!(t.lookup('c'), Some("11019722839397809329"))
}

#[test]
fn parse_access_client_id() {
let t = Token::parse(ACCESS_TOKEN_CLIENT_ID).unwrap();
assert_eq!(t.signature.to_bytes(), "aEPOxMwUriGEv2qc7Pb672ygy-6VeJ-8VrX3jmwalZr7xygU4izyCWxiT7IXfybnNGIsk1FQPb0RRVPx1s2UCw==".from_base64().unwrap()[..]);
assert_eq!(t.version, 1);
assert_eq!(t.key_idx, 1);
assert_eq!(t.timestamp, 1466770783);
assert_eq!(t.token_tag, None);
assert_eq!(t.token_type, TokenType::Access);
assert_eq!(t.lookup('u'), Some("6562d941-4f40-4db4-b96e-56a06d71c2c3"));
assert_eq!(t.lookup('c'), Some("11019722839397809329"));
assert_eq!(t.lookup('i'), Some("deadbeef"));
}

#[test]
fn parse_user() {
let t = Token::parse(USER_TOKEN).unwrap();
Expand All @@ -258,6 +278,20 @@ mod tests {
assert_eq!(t.lookup('r'), Some("4feacc"))
}

#[test]
fn parse_user_client_id() {
let t = Token::parse(USER_TOKEN_CLIENT_ID).unwrap();
assert_eq!(t.signature.to_bytes(), "vpJs7PEgwtsuzGlMY0-Vqs22s8o9ZDlp7wJrPmhCgIfg0NoTAxvxq5OtknabLMfNTEW9amn5tyeUM7tbFZABBA==".from_base64().unwrap()[..]);
assert_eq!(t.version, 1);
assert_eq!(t.key_idx, 1);
assert_eq!(t.timestamp, 1466770905);
assert_eq!(t.token_tag, None);
assert_eq!(t.token_type, TokenType::User);
assert_eq!(t.lookup('u'), Some("6562d941-4f40-4db4-b96e-56a06d71c2c3"));
assert_eq!(t.lookup('r'), Some("4feacc"));
assert_eq!(t.lookup('i'), Some("deadbeef"));
}

#[test]
fn parse_bot() {
let t = Token::parse(BOT_TOKEN).unwrap();
Expand Down
11 changes: 11 additions & 0 deletions libs/wire-api/src/Wire/API/Routes/Public.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
module Wire.API.Routes.Public
( -- * nginz combinators
ZUser,
ZClient,
ZLocalUser,
ZConn,
ZOptUser,
Expand Down Expand Up @@ -73,6 +74,7 @@ data ZType
ZAuthUser
| -- | Same as 'ZAuthUser', but return a 'Local UserId' using the domain in the context
ZLocalAuthUser
| ZAuthClient
| -- | Get a 'ConnId' from the Z-Conn header
ZAuthConn
| ZAuthBot
Expand Down Expand Up @@ -110,6 +112,13 @@ instance IsZType 'ZAuthUser ctx where

qualifyZParam _ = id

instance IsZType 'ZAuthClient ctx where
type ZHeader 'ZAuthClient = "Z-Client"
type ZParam 'ZAuthClient = ClientId
type ZQualifiedParam 'ZAuthClient = ClientId

qualifyZParam _ = id

instance IsZType 'ZAuthConn ctx where
type ZHeader 'ZAuthConn = "Z-Connection"
type ZParam 'ZAuthConn = ConnId
Expand Down Expand Up @@ -158,6 +167,8 @@ type ZLocalUser = ZAuthServant 'ZLocalAuthUser InternalAuthDefOpts

type ZUser = ZAuthServant 'ZAuthUser InternalAuthDefOpts

type ZClient = ZAuthServant 'ZAuthClient InternalAuthDefOpts

type ZConn = ZAuthServant 'ZAuthConn InternalAuthDefOpts

type ZBot = ZAuthServant 'ZAuthBot InternalAuthDefOpts
Expand Down
3 changes: 2 additions & 1 deletion libs/wire-api/src/Wire/API/Routes/Public/Brig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1151,9 +1151,10 @@ type AuthAPI =
\ Every other combination is invalid.\
\ Access tokens can be given as query parameter or authorisation\
\ header, with the latter being preferred."
:> QueryParam "client_id" ClientId
:> Cookies '["zuid" ::: SomeUserToken]
:> CanThrow 'BadCredentials
:> Bearer SomeAccessToken
:> CanThrow 'BadCredentials
:> MultiVerb1 'POST '[JSON] TokenResponse
)
:<|> Named
Expand Down
2 changes: 1 addition & 1 deletion libs/wire-api/src/Wire/API/User/Auth.hs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ import Data.ByteString.Conversion
import qualified Data.ByteString.Lazy as LBS
import Data.Code as Code
import Data.Handle (Handle)
import Data.Id (UserId)
import Data.Id
import Data.Json.Util
import Data.Misc (PlainTextPassword (..))
import Data.SOP
Expand Down
6 changes: 3 additions & 3 deletions libs/zauth/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
{ mkDerivation, attoparsec, base, base64-bytestring, bytestring
, bytestring-conversion, errors, exceptions, gitignoreSource
, imports, lens, lib, mtl, mwc-random, optparse-applicative
, sodium-crypto-sign, tasty, tasty-hunit, tasty-quickcheck, time
, uuid, vector
, sodium-crypto-sign, tasty, tasty-hunit, tasty-quickcheck, text
, time, uuid, vector
}:
mkDerivation {
pname = "zauth";
Expand All @@ -25,7 +25,7 @@ mkDerivation {
];
testHaskellDepends = [
base bytestring bytestring-conversion errors imports lens
sodium-crypto-sign tasty tasty-hunit tasty-quickcheck uuid
sodium-crypto-sign tasty tasty-hunit tasty-quickcheck text uuid
];
description = "Creation and validation of signed tokens";
license = lib.licenses.agpl3Only;
Expand Down
8 changes: 4 additions & 4 deletions libs/zauth/main/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -82,23 +82,23 @@ go CreateSession o = do
let u = uuid . head $ o ^. dat
case fromByteString ((o ^. dat) !! 1) of
Nothing -> error "invalid random int"
Just rn -> runCreate' o $ toByteString <$> sessionToken (o ^. dur) u rn
Just rn -> runCreate' o $ toByteString <$> sessionToken (o ^. dur) u Nothing rn
go CreateUser o = do
when (length (o ^. dat) /= 2) $
error "invalid --data, must have 2 elements"
let u = uuid . head $ o ^. dat
case fromByteString ((o ^. dat) !! 1) of
Nothing -> error "invalid random int"
Just rn -> runCreate' o $ toByteString <$> userToken (o ^. dur) u rn
Just rn -> runCreate' o $ toByteString <$> userToken (o ^. dur) u Nothing rn
go CreateAccess o = do
when (null (o ^. dat)) $
error "invalid --data, must have 1 or 2 elements"
let u = uuid . head $ o ^. dat
case length (o ^. dat) of
1 -> runCreate' o $ toByteString <$> accessToken1 (o ^. dur) u
1 -> runCreate' o $ toByteString <$> accessToken1 (o ^. dur) u Nothing
2 -> case fromByteString ((o ^. dat) !! 1) of
Nothing -> error "invalid connection"
Just c -> runCreate' o $ toByteString <$> accessToken (o ^. dur) u c
Just c -> runCreate' o $ toByteString <$> accessToken (o ^. dur) u Nothing c
_ -> error "invalid --data, must have 1 or 2 elements"
go CreateBot o = do
when (length (o ^. dat) /= 3) $
Expand Down
59 changes: 34 additions & 25 deletions libs/zauth/src/Data/ZAuth/Creation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -97,46 +97,55 @@ withIndex k m = Create $ do
error "withIndex: Key index out of range."
local (const (e {keyIdx = k})) (zauth m)

userToken :: Integer -> UUID -> Word32 -> Create (Token User)
userToken dur usr rnd = do
userToken :: Integer -> UUID -> Maybe Text -> Word32 -> Create (Token User)
userToken dur usr cli rnd = do
d <- expiry dur
newToken d U Nothing (mkUser usr rnd)
newToken d U Nothing (mkUser usr cli rnd)

sessionToken :: Integer -> UUID -> Word32 -> Create (Token User)
sessionToken dur usr rnd = do
sessionToken :: Integer -> UUID -> Maybe Text -> Word32 -> Create (Token User)
sessionToken dur usr cli rnd = do
d <- expiry dur
newToken d U (Just S) (mkUser usr rnd)
newToken d U (Just S) (mkUser usr cli rnd)

-- | Create an access token taking a duration, userId and a (random) number that can be used as connection identifier
accessToken :: Integer -> UUID -> Word64 -> Create (Token Access)
accessToken dur usr con = do
-- | Create an access token taking a duration, userId, clientId and a (random)
-- number that can be used as connection identifier
accessToken :: Integer -> UUID -> Maybe Text -> Word64 -> Create (Token Access)
accessToken dur usr cid con = do
d <- expiry dur
newToken d A Nothing (mkAccess usr con)
newToken d A Nothing (mkAccess usr cid con)

-- | Create an access token taking a duration and userId. Similar to 'accessToken', except that the connection identifier is randomly generated.
accessToken1 :: Integer -> UUID -> Create (Token Access)
accessToken1 dur usr = do
-- | Create an access token taking a duration, userId and clientId.
-- Similar to 'accessToken', except that the connection identifier is randomly
-- generated.
accessToken1 :: Integer -> UUID -> Maybe Text -> Create (Token Access)
accessToken1 dur usr cid = do
g <- Create $ asks randGen
d <- liftIO $ asGenIO (uniform :: GenIO -> IO Word64) g
accessToken dur usr d
accessToken dur usr cid d

legalHoldUserToken :: Integer -> UUID -> Word32 -> Create (Token LegalHoldUser)
legalHoldUserToken dur usr rnd = do
legalHoldUserToken :: Integer -> UUID -> Maybe Text -> Word32 -> Create (Token LegalHoldUser)
legalHoldUserToken dur usr cli rnd = do
d <- expiry dur
newToken d LU Nothing (mkLegalHoldUser usr rnd)

-- | Create a legal hold access token taking a duration, userId and a (random) number that can be used as connection identifier
legalHoldAccessToken :: Integer -> UUID -> Word64 -> Create (Token LegalHoldAccess)
legalHoldAccessToken dur usr con = do
newToken d LU Nothing (mkLegalHoldUser usr cli rnd)

-- | Create a legal hold access token taking a duration, userId, clientId and a
-- (random) number that can be used as connection identifier
legalHoldAccessToken ::
Integer ->
UUID ->
Maybe Text ->
Word64 ->
Create (Token LegalHoldAccess)
legalHoldAccessToken dur usr cid con = do
d <- expiry dur
newToken d LA Nothing (mkLegalHoldAccess usr con)
newToken d LA Nothing (mkLegalHoldAccess usr cid con)

-- | Create a legal hold access token taking a duration, userId. Similar to 'legalHoldAccessToken', except that the connection identifier is randomly generated.
legalHoldAccessToken1 :: Integer -> UUID -> Create (Token LegalHoldAccess)
legalHoldAccessToken1 dur usr = do
legalHoldAccessToken1 :: Integer -> UUID -> Maybe Text -> Create (Token LegalHoldAccess)
legalHoldAccessToken1 dur usr cid = do
g <- Create $ asks randGen
d <- liftIO $ asGenIO (uniform :: GenIO -> IO Word64) g
legalHoldAccessToken dur usr d
legalHoldAccessToken dur usr cid d

botToken :: UUID -> UUID -> UUID -> Create (Token Bot)
botToken pid bid cnv = newToken (-1) B Nothing (mkBot pid bid cnv)
Expand Down
22 changes: 16 additions & 6 deletions libs/zauth/src/Data/ZAuth/Token.hs
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ module Data.ZAuth.Token
-- * Access body
Access,
userId,
clientId,
connection,
mkAccess,

-- * User body
User,
user,
client,
rand,
mkUser,

Expand Down Expand Up @@ -126,13 +128,15 @@ data Header = Header

data Access = Access
{ _userId :: !UUID,
_clientId :: Maybe Text,
-- | 'ConnId' is derived from this.
_connection :: !Word64
}
deriving (Eq, Show)

data User = User
{ _user :: !UUID,
_client :: Maybe Text,
_rand :: !Word32
}
deriving (Eq, Show)
Expand Down Expand Up @@ -238,10 +242,10 @@ mkToken = Token
mkHeader :: Int -> Int -> Integer -> Type -> Maybe Tag -> Header
mkHeader = Header

mkAccess :: UUID -> Word64 -> Access
mkAccess :: UUID -> Maybe Text -> Word64 -> Access
mkAccess = Access

mkUser :: UUID -> Word32 -> User
mkUser :: UUID -> Maybe Text -> Word32 -> User
mkUser = User

mkBot :: UUID -> UUID -> UUID -> Bot
Expand All @@ -250,11 +254,11 @@ mkBot = Bot
mkProvider :: UUID -> Provider
mkProvider = Provider

mkLegalHoldAccess :: UUID -> Word64 -> LegalHoldAccess
mkLegalHoldAccess uid cid = LegalHoldAccess $ Access uid cid
mkLegalHoldAccess :: UUID -> Maybe Text -> Word64 -> LegalHoldAccess
mkLegalHoldAccess uid clt con = LegalHoldAccess $ Access uid clt con

mkLegalHoldUser :: UUID -> Word32 -> LegalHoldUser
mkLegalHoldUser uid r = LegalHoldUser $ User uid r
mkLegalHoldUser :: UUID -> Maybe Text -> Word32 -> LegalHoldUser
mkLegalHoldUser uid cid r = LegalHoldUser $ User uid cid r

-----------------------------------------------------------------------------
-- Reading
Expand Down Expand Up @@ -295,12 +299,14 @@ readAccessBody :: Properties -> Maybe Access
readAccessBody t =
Access
<$> (lookup "u" t >>= fromLazyASCIIBytes)
<*> pure (lookup "i" t >>= fromByteString')
<*> (lookup "c" t >>= fromByteString')

readUserBody :: Properties -> Maybe User
readUserBody t =
User
<$> (lookup "u" t >>= fromLazyASCIIBytes)
<*> pure (lookup "i" t >>= fromByteString')
<*> (lookup "r" t >>= fmap fromHex . fromByteString')

readBotBody :: Properties -> Maybe Bot
Expand Down Expand Up @@ -346,6 +352,7 @@ writeHeader t =
instance ToByteString Access where
builder t =
field "u" (toLazyASCIIBytes $ t ^. userId)
<> foldMap (\c -> dot <> field "i" c) (t ^. clientId)
<> dot
<> field "c" (t ^. connection)

Expand All @@ -354,6 +361,7 @@ instance ToByteString User where
field "u" (toLazyASCIIBytes $ t ^. user)
<> dot
<> field "r" (Hex (t ^. rand))
<> foldMap (\c -> dot <> field "i" c) (t ^. client)

instance ToByteString Bot where
builder t =
Expand All @@ -369,6 +377,7 @@ instance ToByteString Provider where
instance ToByteString LegalHoldAccess where
builder t =
field "u" (toLazyASCIIBytes $ t ^. legalHoldAccess . userId)
<> foldMap (\c -> dot <> field "i" c) (t ^. legalHoldAccess . clientId)
<> dot
<> field "c" (t ^. legalHoldAccess . connection)

Expand All @@ -377,6 +386,7 @@ instance ToByteString LegalHoldUser where
field "u" (toLazyASCIIBytes $ t ^. legalHoldUser . user)
<> dot
<> field "r" (Hex (t ^. legalHoldUser . rand))
<> foldMap (\c -> dot <> field "i" c) (t ^. legalHoldUser . client)

instance ToByteString Type where
builder A = char8 'a'
Expand Down
Loading