{-# LANGUAGE OverloadedStrings, BangPatterns #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Network.Wreq.Internal.AWS
(
signRequest,
signRequestFull
) where
import Control.Applicative ((<$>))
import Control.Lens ((%~), (^.), (&), to)
import Crypto.MAC.HMAC (HMAC (..), hmac, hmacGetDigest)
import Data.ByteString.Base16 as HEX (encode)
import Data.ByteArray (convert)
import Data.Char (toLower)
import Data.List (sort)
import Data.Monoid ((<>))
import Data.Time.Clock (getCurrentTime)
import Data.Time.Format (formatTime)
import Data.Time.Locale.Compat (defaultTimeLocale)
import Data.Time.LocalTime (utc, utcToLocalTime)
import Network.HTTP.Types (parseSimpleQuery, urlEncode)
import Network.Wreq.Internal.Lens
import Network.Wreq.Internal.Types (AWSAuthVersion(..))
import qualified Crypto.Hash as CT (Digest, SHA256, hash, hashlazy)
import qualified Data.ByteString.Char8 as S
import qualified Data.ByteString.Lazy.Char8 as L
import qualified Data.CaseInsensitive as CI (original)
import qualified Data.HashSet as HashSet
import qualified Network.HTTP.Client as HTTP
signRequest :: AWSAuthVersion -> S.ByteString -> S.ByteString ->
Request -> IO Request
signRequest :: AWSAuthVersion -> ByteString -> ByteString -> Request -> IO Request
signRequest AWSAuthVersion
AWSv4 ByteString
aid ByteString
key Request
r = AWSAuthVersion
-> ByteString
-> ByteString
-> Maybe (ByteString, ByteString)
-> Request
-> IO Request
signRequestFull AWSAuthVersion
AWSv4 ByteString
aid ByteString
key forall a. Maybe a
Nothing Request
r
hexSha256Hash :: S.ByteString -> S.ByteString
hexSha256Hash :: ByteString -> ByteString
hexSha256Hash ByteString
dta =
let digest :: Digest SHA256
digest = forall ba a.
(ByteArrayAccess ba, HashAlgorithm a) =>
ba -> Digest a
CT.hash ByteString
dta :: CT.Digest CT.SHA256
in String -> ByteString
S.pack (forall a. Show a => a -> String
show Digest SHA256
digest)
hexSha256HashLazy :: L.ByteString -> S.ByteString
hexSha256HashLazy :: ByteString -> ByteString
hexSha256HashLazy ByteString
dta =
let digest :: Digest SHA256
digest = forall a. HashAlgorithm a => ByteString -> Digest a
CT.hashlazy ByteString
dta :: CT.Digest CT.SHA256
in String -> ByteString
S.pack (forall a. Show a => a -> String
show Digest SHA256
digest)
signRequestFull :: AWSAuthVersion -> S.ByteString -> S.ByteString -> Maybe (S.ByteString, S.ByteString) -> Request -> IO Request
signRequestFull :: AWSAuthVersion
-> ByteString
-> ByteString
-> Maybe (ByteString, ByteString)
-> Request
-> IO Request
signRequestFull AWSAuthVersion
AWSv4 = ByteString
-> ByteString
-> Maybe (ByteString, ByteString)
-> Request
-> IO Request
signRequestV4
signRequestV4 :: S.ByteString -> S.ByteString -> Maybe (S.ByteString, S.ByteString) -> Request -> IO Request
signRequestV4 :: ByteString
-> ByteString
-> Maybe (ByteString, ByteString)
-> Request
-> IO Request
signRequestV4 ByteString
key ByteString
secret Maybe (ByteString, ByteString)
serviceRegion Request
request = do
!ByteString
ts <- IO ByteString
timestamp
let origHost :: ByteString
origHost = Request
request forall s a. s -> Getting a s a -> a
^. Lens' Request ByteString
host
runscopeBucketAuth :: Maybe ByteString
runscopeBucketAuth =
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup HeaderName
"Runscope-Bucket-Auth" forall a b. (a -> b) -> a -> b
$ Request
request forall s a. s -> Getting a s a -> a
^. Lens' Request [(HeaderName, ByteString)]
requestHeaders
noRunscopeHost :: ByteString
noRunscopeHost = ByteString -> ByteString
removeRunscope ByteString
origHost
(ByteString
service, ByteString
region) = case Maybe (ByteString, ByteString)
serviceRegion of
Maybe (ByteString, ByteString)
Nothing -> ByteString -> (ByteString, ByteString)
serviceAndRegion ByteString
noRunscopeHost
Just (ByteString
a, ByteString
b) -> (ByteString
a, ByteString
b)
date :: ByteString
date = (Char -> Bool) -> ByteString -> ByteString
S.takeWhile (forall a. Eq a => a -> a -> Bool
/= Char
'T') ByteString
ts
hashedPayload :: ByteString
hashedPayload
| Request
request forall s a. s -> Getting a s a -> a
^. Lens' Request ByteString
method forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ByteString
"POST", ByteString
"PUT"] = Request -> ByteString
payloadHash Request
req
| Bool
otherwise = ByteString -> ByteString
hexSha256Hash ByteString
""
req :: Request
req = Request
request forall a b. a -> (a -> b) -> b
& Lens' Request [(HeaderName, ByteString)]
requestHeaders forall s t a b. ASetter s t a b -> (a -> b) -> s -> t
%~
(([ (HeaderName
"host", ByteString
noRunscopeHost)
, (HeaderName
"x-amz-date", ByteString
ts)] forall a. [a] -> [a] -> [a]
++
[(HeaderName
"x-amz-content-sha256", ByteString
hashedPayload) | ByteString
service forall a. Eq a => a -> a -> Bool
== ByteString
"s3"]) forall a. [a] -> [a] -> [a]
++)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. Eq a => a -> [(a, b)] -> [(a, b)]
deleteKey HeaderName
"Runscope-Bucket-Auth"
let encodePath :: ByteString -> ByteString
encodePath ByteString
p = ByteString -> [ByteString] -> ByteString
S.intercalate ByteString
"/" forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (Bool -> ByteString -> ByteString
urlEncode Bool
False) forall a b. (a -> b) -> a -> b
$ Char -> ByteString -> [ByteString]
S.split Char
'/' ByteString
p
let hl :: [(HeaderName, ByteString)]
hl = Request
req forall s a. s -> Getting a s a -> a
^. Lens' Request [(HeaderName, ByteString)]
requestHeaders forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (p :: * -> * -> *) (f :: * -> *) s a.
(Profunctor p, Contravariant f) =>
(s -> a) -> Optic' p f s a
to forall a. Ord a => [a] -> [a]
sort
signedHeaders :: ByteString
signedHeaders = ByteString -> [ByteString] -> ByteString
S.intercalate ByteString
";" forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map (HeaderName -> ByteString
lowerCI forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst) forall a b. (a -> b) -> a -> b
$ [(HeaderName, ByteString)]
hl
canonicalReq :: ByteString
canonicalReq = ByteString -> [ByteString] -> ByteString
S.intercalate ByteString
"\n" [
Request
req forall s a. s -> Getting a s a -> a
^. Lens' Request ByteString
method
, ByteString -> ByteString
encodePath (Request
req forall s a. s -> Getting a s a -> a
^. Lens' Request ByteString
path)
, ByteString -> [ByteString] -> ByteString
S.intercalate ByteString
"&"
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map (\(ByteString
k,ByteString
v) -> Bool -> ByteString -> ByteString
urlEncode Bool
True ByteString
k forall a. Semigroup a => a -> a -> a
<> ByteString
"=" forall a. Semigroup a => a -> a -> a
<> Bool -> ByteString -> ByteString
urlEncode Bool
True ByteString
v)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Ord a => [a] -> [a]
sort forall a b. (a -> b) -> a -> b
$
ByteString -> [(ByteString, ByteString)]
parseSimpleQuery forall a b. (a -> b) -> a -> b
$ Request
req forall s a. s -> Getting a s a -> a
^. Lens' Request ByteString
queryString
, [ByteString] -> ByteString
S.unlines
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map (\(HeaderName
k,ByteString
v) -> HeaderName -> ByteString
lowerCI HeaderName
k forall a. Semigroup a => a -> a -> a
<> ByteString
":" forall a. Semigroup a => a -> a -> a
<> forall {a}. a -> a
trimHeaderValue ByteString
v) forall a b. (a -> b) -> a -> b
$ [(HeaderName, ByteString)]
hl
, ByteString
signedHeaders
, ByteString
hashedPayload
]
let dateScope :: ByteString
dateScope = ByteString -> [ByteString] -> ByteString
S.intercalate ByteString
"/" [ByteString
date, ByteString
region, ByteString
service, ByteString
"aws4_request"]
stringToSign :: ByteString
stringToSign = ByteString -> [ByteString] -> ByteString
S.intercalate ByteString
"\n" [
ByteString
"AWS4-HMAC-SHA256"
, ByteString
ts
, ByteString
dateScope
, ByteString -> ByteString
hexSha256Hash ByteString
canonicalReq
]
let signature :: ByteString
signature = (ByteString
"AWS4" forall a. Semigroup a => a -> a -> a
<> ByteString
secret) forall a b. a -> (a -> b) -> b
&
ByteString -> ByteString -> ByteString
hmac' ByteString
date forall a b. a -> (a -> b) -> b
& ByteString -> ByteString -> ByteString
hmac' ByteString
region forall a b. a -> (a -> b) -> b
& ByteString -> ByteString -> ByteString
hmac' ByteString
service forall a b. a -> (a -> b) -> b
&
ByteString -> ByteString -> ByteString
hmac' ByteString
"aws4_request" forall a b. a -> (a -> b) -> b
& ByteString -> ByteString -> ByteString
hmac' ByteString
stringToSign forall a b. a -> (a -> b) -> b
& ByteString -> ByteString
HEX.encode
authorization :: ByteString
authorization = ByteString -> [ByteString] -> ByteString
S.intercalate ByteString
", " [
ByteString
"AWS4-HMAC-SHA256 Credential=" forall a. Semigroup a => a -> a -> a
<> ByteString
key forall a. Semigroup a => a -> a -> a
<> ByteString
"/" forall a. Semigroup a => a -> a -> a
<> ByteString
dateScope
, ByteString
"SignedHeaders=" forall a. Semigroup a => a -> a -> a
<> ByteString
signedHeaders
, ByteString
"Signature=" forall a. Semigroup a => a -> a -> a
<> ByteString
signature
]
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ HeaderName -> ByteString -> Request -> Request
setHeader HeaderName
"host" ByteString
origHost
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall b a. b -> (a -> b) -> Maybe a -> b
maybe forall {a}. a -> a
id (HeaderName -> ByteString -> Request -> Request
setHeader HeaderName
"Runscope-Bucket-Auth") Maybe ByteString
runscopeBucketAuth
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> HeaderName -> ByteString -> Request -> Request
setHeader HeaderName
"authorization" ByteString
authorization forall a b. (a -> b) -> a -> b
$ Request
req
where
lowerCI :: HeaderName -> ByteString
lowerCI = (Char -> Char) -> ByteString -> ByteString
S.map Char -> Char
toLower forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall s. CI s -> s
CI.original
trimHeaderValue :: a -> a
trimHeaderValue =
forall {a}. a -> a
id
timestamp :: IO ByteString
timestamp = UTCTime -> ByteString
render forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO UTCTime
getCurrentTime
where render :: UTCTime -> ByteString
render = String -> ByteString
S.pack forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall t. FormatTime t => TimeLocale -> String -> t -> String
formatTime TimeLocale
defaultTimeLocale String
"%Y%m%dT%H%M%SZ" forall b c a. (b -> c) -> (a -> b) -> a -> c
.
TimeZone -> UTCTime -> LocalTime
utcToLocalTime TimeZone
utc
hmac' :: S.ByteString -> S.ByteString -> S.ByteString
hmac' :: ByteString -> ByteString -> ByteString
hmac' ByteString
s ByteString
k = forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
bin -> bout
convert (forall a. HMAC a -> Digest a
hmacGetDigest HMAC SHA256
h)
where h :: HMAC SHA256
h = forall key message a.
(ByteArrayAccess key, ByteArrayAccess message, HashAlgorithm a) =>
key -> message -> HMAC a
hmac ByteString
k ByteString
s :: (HMAC CT.SHA256)
payloadHash :: Request -> S.ByteString
payloadHash :: Request -> ByteString
payloadHash Request
req =
case Request -> RequestBody
HTTP.requestBody Request
req of
HTTP.RequestBodyBS ByteString
bs -> ByteString -> ByteString
hexSha256Hash ByteString
bs
HTTP.RequestBodyLBS ByteString
lbs -> ByteString -> ByteString
hexSha256HashLazy ByteString
lbs
RequestBody
_ -> forall a. HasCallStack => String -> a
error String
"addTmpPayloadHashHeader: unexpected request body type"
serviceAndRegion :: S.ByteString -> (S.ByteString, S.ByteString)
serviceAndRegion :: ByteString -> (ByteString, ByteString)
serviceAndRegion ByteString
endpoint
| ByteString
".s3.amazonaws.com" ByteString -> ByteString -> Bool
`S.isSuffixOf` ByteString
endpoint =
(ByteString
"s3", ByteString
"us-east-1")
| ByteString
".s3-external-1.amazonaws.com" ByteString -> ByteString -> Bool
`S.isSuffixOf` ByteString
endpoint =
(ByteString
"s3", ByteString
"us-east-1")
| ByteString
".s3-" ByteString -> ByteString -> Bool
`S.isInfixOf` ByteString
endpoint =
(ByteString
"s3", ByteString -> ByteString
regionInS3VHost ByteString
endpoint)
| ByteString
endpoint forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ByteString
"s3.amazonaws.com", ByteString
"s3-external-1.amazonaws.com"] =
(ByteString
"s3", ByteString
"us-east-1")
| Char -> ByteString -> ByteString
servicePrefix Char
'-' ByteString
endpoint forall a. Eq a => a -> a -> Bool
== ByteString
"s3" =
let region :: ByteString
region = (Char -> Bool) -> ByteString -> ByteString
S.takeWhile (forall a. Eq a => a -> a -> Bool
/= Char
'.') forall a b. (a -> b) -> a -> b
$ Int -> ByteString -> ByteString
S.drop Int
3 ByteString
endpoint
in (ByteString
"s3", ByteString
region)
| ByteString
endpoint forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ByteString
"sts.amazonaws.com"] =
(ByteString
"sts", ByteString
"us-east-1")
| ByteString
".execute-api." ByteString -> ByteString -> Bool
`S.isInfixOf` ByteString
endpoint =
let ByteString
gateway:ByteString
service:ByteString
region:[ByteString]
_ = Char -> ByteString -> [ByteString]
S.split Char
'.' ByteString
endpoint
in (ByteString
service, ByteString
region)
| ByteString
".es.amazonaws.com" ByteString -> ByteString -> Bool
`S.isSuffixOf` ByteString
endpoint =
let ByteString
_:ByteString
region:[ByteString]
_ = Char -> ByteString -> [ByteString]
S.split Char
'.' ByteString
endpoint
in (ByteString
"es", ByteString
region)
| ByteString
svc forall a. (Eq a, Hashable a) => a -> HashSet a -> Bool
`HashSet.member` HashSet ByteString
noRegion =
(ByteString
svc, ByteString
"us-east-1")
| Bool
otherwise =
let ByteString
service:ByteString
region:[ByteString]
_ = Char -> ByteString -> [ByteString]
S.split Char
'.' ByteString
endpoint
in (ByteString
service, ByteString
region)
where
svc :: ByteString
svc = Char -> ByteString -> ByteString
servicePrefix Char
'.' ByteString
endpoint
servicePrefix :: Char -> ByteString -> ByteString
servicePrefix Char
c = (Char -> Char) -> ByteString -> ByteString
S.map Char -> Char
toLower forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> ByteString -> ByteString
S.takeWhile (forall a. Eq a => a -> a -> Bool
/= Char
c)
regionInS3VHost :: ByteString -> ByteString
regionInS3VHost ByteString
s =
(Char -> Bool) -> ByteString -> ByteString
S.takeWhile (forall a. Eq a => a -> a -> Bool
/= Char
'.')
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
S.reverse
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString -> (ByteString, ByteString)
S.breakSubstring (String -> ByteString
S.pack String
"-3s.")
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
S.reverse
forall a b. (a -> b) -> a -> b
$ ByteString
s
noRegion :: HashSet ByteString
noRegion = forall a. (Eq a, Hashable a) => [a] -> HashSet a
HashSet.fromList [ByteString
"iam", ByteString
"importexport", ByteString
"route53", ByteString
"cloudfront"]
removeRunscope :: S.ByteString -> S.ByteString
removeRunscope :: ByteString -> ByteString
removeRunscope ByteString
hostname
| ByteString
".runscope.net" ByteString -> ByteString -> Bool
`S.isSuffixOf` ByteString
hostname =
[ByteString] -> ByteString
S.concat forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
Prelude.map (forall {a}. (Eq a, IsString a) => a -> a
p2 forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall {a}. (Eq a, IsString a) => a -> a
p1) forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> [ByteString]
S.group
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
S.reverse forall b c a. (b -> c) -> (a -> b) -> a -> c
. HasCallStack => ByteString -> ByteString
S.tail forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> ByteString -> ByteString
S.dropWhile (forall a. Eq a => a -> a -> Bool
/= Char
'-') forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
S.reverse
forall a b. (a -> b) -> a -> b
$ ByteString
hostname
| Bool
otherwise = ByteString
hostname
where p1 :: a -> a
p1 a
"-" = a
"."
p1 a
other = a
other
p2 :: a -> a
p2 a
"--" = a
"-"
p2 a
other = a
other