on master help sign in
Working on adding Auth options
9494b56mrkkrp
Network/HTTP/Req.hs+94−28
@@ -81,6 +81,7 @@
81{-# LANGUAGE FlexibleInstances #-}
81{-# LANGUAGE FlexibleInstances #-}
82{-# LANGUAGE GeneralizedNewtypeDeriving #-}
82{-# LANGUAGE GeneralizedNewtypeDeriving #-}
83{-# LANGUAGE KindSignatures #-}
83{-# LANGUAGE KindSignatures #-}
84+{-# LANGUAGE OverloadedStrings #-}
84{-# LANGUAGE RecordWildCards #-}
85{-# LANGUAGE RecordWildCards #-}
85{-# LANGUAGE ScopedTypeVariables #-}
86{-# LANGUAGE ScopedTypeVariables #-}
86{-# LANGUAGE TypeFamilies #-}
87{-# LANGUAGE TypeFamilies #-}
@@ -125,12 +126,19 @@ module Network.HTTP.Req
125 , header
126 , header
126 -- *** Cookies
127 -- *** Cookies
127 -- *** Authentication
128 -- *** Authentication
129+ -- $request-optional-parameters-authentication
130+ , basicAuth
131+ , oAuth1
132+ , oAuth2Bearer
133+ , oAuth2Token
134+ , awsAuth
128 -- *** Other
135 -- *** Other
129 , port
136 , port
130 -- * Response
137 -- * Response
131 , HttpResponse (..)
138 , HttpResponse (..)
132 -- * Other
139 -- * Other
133− , CanHaveBody (..) )
140+ , CanHaveBody (..)
141+ , Scheme (..) )
134where
142where
135
143
136import Control.Arrow (first, second)
144import Control.Arrow (first, second)
@@ -173,16 +181,16 @@ import qualified Network.HTTP.Types as Y
173-- TODO Finish docs of this function when the package is more developed.
181-- TODO Finish docs of this function when the package is more developed.
174
182
175req
183req
176− :: forall m method body response.
184+ :: forall m method body response scheme.
177 ( MonadHttp m
185 ( MonadHttp m
178 , HttpMethod method
186 , HttpMethod method
179 , HttpBody body
187 , HttpBody body
180 , HttpResponse response
188 , HttpResponse response
181 , AllowsBody method ~ ProvidesBody body )
189 , AllowsBody method ~ ProvidesBody body )
182 => method -- ^ HTTP method
190 => method -- ^ HTTP method
183− -> Url -- ^ 'Url' — location of resource
191+ -> Url scheme -- ^ 'Url' — location of resource
184 -> body -- ^ Body of the request
192 -> body -- ^ Body of the request
185− -> Option -- ^ Collection of optional parameters
193+ -> Option scheme -- ^ Collection of optional parameters
186 -> m response -- ^ Response
194 -> m response -- ^ Response
187req method url body options = do
195req method url body options = do
188 config <- getHttpConfig
196 config <- getHttpConfig
@@ -422,7 +430,7 @@ instance HttpMethod method => RequestComponent (Womb "method" method) where
422-- > https "юникод.рф"
430-- > https "юникод.рф"
423-- > -- https://%D1%8E%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4.%D1%80%D1%84
431-- > -- https://%D1%8E%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4.%D1%80%D1%84
424
432
425−data Url = Url Bool (NonEmpty Text)
433+data Url (scheme :: Scheme) = Url Bool (NonEmpty Text)
426 -- NOTE The first 'Bool' value specifies if the 'Url' has “https” as its
434 -- NOTE The first 'Bool' value specifies if the 'Url' has “https” as its
427 -- scheme (otherwise “http” is assumed). The second value is path segments
435 -- scheme (otherwise “http” is assumed). The second value is path segments
428 -- in reversed order.
436 -- in reversed order.
@@ -431,22 +439,22 @@ data Url = Url Bool (NonEmpty Text)
431-- | Given host name, produce a 'Url' which have “http” as its scheme and
439-- | Given host name, produce a 'Url' which have “http” as its scheme and
432-- empty path. This also sets port to @80@.
440-- empty path. This also sets port to @80@.
433
441
434−http :: Text -> Url
442+http :: Text -> Url 'Http
435http = Url False . pure
443http = Url False . pure
436
444
437-- | Given host name, produce a 'Url' which have “https” as its scheme and
445-- | Given host name, produce a 'Url' which have “https” as its scheme and
438-- empty path. This also sets port to @443@.
446-- empty path. This also sets port to @443@.
439
447
440−https :: Text -> Url
448+https :: Text -> Url 'Https
441https = Url True . pure
449https = Url True . pure
442
450
443-- | Grow given 'Url' appending a single path segment to it.
451-- | Grow given 'Url' appending a single path segment to it.
444
452
445infixl 5 /:
453infixl 5 /:
446−(/:) :: Url -> Text -> Url
454+(/:) :: Url scheme -> Text -> Url scheme
447Url secure path /: segment = Url secure (NE.cons segment path)
455Url secure path /: segment = Url secure (NE.cons segment path)
448
456
449−instance RequestComponent Url where
457+instance RequestComponent (Url scheme) where
450 getRequestMod (Url secure segments) = Endo $ \x ->
458 getRequestMod (Url secure segments) = Endo $ \x ->
451 let (host :| path) = NE.reverse segments in
459 let (host :| path) = NE.reverse segments in
452 x { L.secure = secure
460 x { L.secure = secure
@@ -487,7 +495,7 @@ instance HttpBody b => RequestComponent (Womb "body" b) where
487
495
488-- TODO We need examples here.
496-- TODO We need examples here.
489
497
490−newtype Option = Option (Endo (Y.QueryText, L.Request))
498+newtype Option (scheme :: Scheme) = Option (Endo (Y.QueryText, L.Request))
491 -- NOTE 'QueryText' is just [(Text, Maybe Text)], we keep it along with
499 -- NOTE 'QueryText' is just [(Text, Maybe Text)], we keep it along with
492 -- Request to avoid appending to existing query string in request every
500 -- Request to avoid appending to existing query string in request every
493 -- time new parameter is added.
501 -- time new parameter is added.
@@ -496,16 +504,16 @@ newtype Option = Option (Endo (Y.QueryText, L.Request))
496-- | A helper to create an 'Option' that modifies only collection of query
504-- | A helper to create an 'Option' that modifies only collection of query
497-- parameters. This helper is not a part of public API.
505-- parameters. This helper is not a part of public API.
498
506
499−withQueryParams :: (Y.QueryText -> Y.QueryText) -> Option
507+withQueryParams :: (Y.QueryText -> Y.QueryText) -> Option scheme
500withQueryParams = Option . Endo . first
508withQueryParams = Option . Endo . first
501
509
502-- | A helper to create an 'Option' that modifies only 'L.Request'. This
510-- | A helper to create an 'Option' that modifies only 'L.Request'. This
503-- helper is not a part of public API.
511-- helper is not a part of public API.
504
512
505−withRequest :: (L.Request -> L.Request) -> Option
513+withRequest :: (L.Request -> L.Request) -> Option scheme
506withRequest = Option . Endo . second
514withRequest = Option . Endo . second
507
515
508−instance RequestComponent Option where
516+instance RequestComponent (Option scheme) where
509 getRequestMod (Option f) = Endo $ \x ->
517 getRequestMod (Option f) = Endo $ \x ->
510 let (qparams, x') = appEndo f ([], x)
518 let (qparams, x') = appEndo f ([], x)
511 query = Y.renderQuery True (Y.queryTextToQuery qparams)
519 query = Y.renderQuery True (Y.queryTextToQuery qparams)
@@ -552,23 +560,20 @@ class QueryParam a where
552
560
553 queryParam :: Text -> Maybe Text -> a
561 queryParam :: Text -> Maybe Text -> a
554
562
555−instance QueryParam Option where
563+instance QueryParam (Option scheme) where
556 queryParam name mvalue = withQueryParams ((:) (name, mvalue))
564 queryParam name mvalue = withQueryParams ((:) (name, mvalue))
557
565
558----------------------------------------------------------------------------
566----------------------------------------------------------------------------
559-- Request — Optional parameters — Headers
567-- Request — Optional parameters — Headers
560
568
561−-- | Create an 'Option' that adds a header. The 'Text' values will be
569+-- | Create an 'Option' that adds a header.
562−-- inserted in UTF-8 encoding.
563
570
564header
571header
565− :: Text -- ^ Header name
572+ :: ByteString -- ^ Header name
566− -> Text -- ^ Header value
573+ -> ByteString -- ^ Header value
567− -> Option
574+ -> Option scheme
568header name value = withRequest $ \x ->
575header name value = withRequest $ \x ->
569− let name' = T.encodeUtf8 name
570− value' = T.encodeUtf8 value
571− in x { L.requestHeaders = (CI.mk name', value') : L.requestHeaders x }
576+ x { L.requestHeaders = (CI.mk name, value) : L.requestHeaders x }
572
577
573----------------------------------------------------------------------------
578----------------------------------------------------------------------------
574-- Request — Optional parameters — Cookies
579-- Request — Optional parameters — Cookies
@@ -578,11 +583,64 @@ header name value = withRequest $ \x ->
578----------------------------------------------------------------------------
583----------------------------------------------------------------------------
579-- Request — Optional parameters — Authentication
584-- Request — Optional parameters — Authentication
580
585
581−-- TODO basicAuth
586+-- $request-optional-parameters-authentication
587+--
588+-- TODO Something. You should always prefer authentication 'Option's to
589+-- manual construction of headers, etc., because it's a better style and
590+-- provides additional type safety that prevents leaking of credentials.
591+
582−-- TODO oAuth1
592+oAuth1
583−-- TODO oAuth2Bearer
593+ :: ByteString -- ^ Consumer token
584−-- TODO oAuth2Token
594+ -> ByteString -- ^ Consumer secret
595+ -> ByteString -- ^ OAuth token
596+ -> ByteString -- ^ OAuth token secret
597+ -> Option scheme
598+oAuth1 = undefined -- TODO
599+
600+-- | The 'Option' adds basic authentication. The 'Text' values will be UTF-8
601+-- encoded.
602+--
603+-- See also: <https://en.wikipedia.org/wiki/Basic_access_authentication>
604+
605+basicAuth
606+ :: ByteString -- ^ Username
607+ -> ByteString -- ^ Password
608+ -> Option 'Https -- ^ Auth 'Option'
609+basicAuth username password = withRequest
610+ (L.applyBasicAuth username password)
611+
612+-- | The 'Option' adds an OAuth2 bearer token. This is treated by many
613+-- services as the equivalent of a username and password.
614+--
615+-- The 'Option' is defined as:
616+--
617+-- > oAuth2Bearer token = header "Authorization" ("Bearer " <> token)
618+
619+oAuth2Bearer
620+ :: ByteString -- ^ Token
621+ -> Option 'Https -- ^ Auth 'Option'
622+oAuth2Bearer token = header "Authorization" ("Bearer " <> token)
623+
624+-- | The 'Option' adds a not-quite-standard OAuth2 bearer token (that seems
625+-- to be used only by GitHub). This will be treated by whatever services
626+-- accept it as the equivalent of a username and password.
627+--
628+-- The 'Option' is defined as:
629+--
630+-- > oAuth2Token token = header "Authorization" ("token" <> token)
631+
632+oAuth2Token
633+ :: ByteString -- ^ Token
634+ -> Option 'Https -- ^ Auth 'Option'
635+oAuth2Token token = header "Authorization" ("token" <> token)
636+
637+-- | The 'Option' adds AWS v4 request signature.
638+
585−-- TODO awsAuth
639+awsAuth
640+ :: ByteString -- ^ AWS access key
641+ -> ByteString -- ^ AWS secret access key
642+ -> Option 'Https -- ^ Auth 'Option'
643+awsAuth accessKey secretKey = undefined -- TODO
586
644
587----------------------------------------------------------------------------
645----------------------------------------------------------------------------
588-- Request — Optional parameters — Other
646-- Request — Optional parameters — Other
@@ -591,7 +649,7 @@ header name value = withRequest $ \x ->
591-- determines default port, @80@ for HTTP and @443@ for HTTPS, this 'Option'
649-- determines default port, @80@ for HTTP and @443@ for HTTPS, this 'Option'
592-- allows to choose arbitrary port overwriting the defaults.
650-- allows to choose arbitrary port overwriting the defaults.
593
651
594−port :: Word -> Option
652+port :: Word -> Option scheme
595port n = withRequest $ \x ->
653port n = withRequest $ \x ->
596 x { L.port = fromIntegral n }
654 x { L.port = fromIntegral n }
597
655
@@ -646,3 +704,11 @@ newtype Womb (tag :: Symbol) a = Womb a
646data CanHaveBody
704data CanHaveBody
647 = CanHaveBody -- ^ Indeed can have a body
705 = CanHaveBody -- ^ Indeed can have a body
648 | NoBody -- ^ Should not have a body
706 | NoBody -- ^ Should not have a body
707+
708+-- | A type-level tag that specifies URL scheme used (and thus if TLS is
709+-- enabled). This is used to force TLS requirement for some authentication
710+-- 'Option's.
711+
712+data Scheme
713+ = Http -- ^ HTTP, no TLS
714+ | Https -- ^ HTTPS
Network/HTTP/Req/AWS.hsnew file+14
@@ -0,0 +1,14 @@
1+-- |
2+-- Module : Network.HTTP.Req.AWS
3+-- Copyright : © 2016 Mark Karpov
4+-- License : BSD 3 clause
5+--
6+-- Maintainer : Mark Karpov <markkarpov@openmailbox.org>
7+-- Stability : experimental
8+-- Portability : portable
9+--
10+-- Implementation of AWS request signature.
11+
12+module Network.HTTP.Req.AWS
13+ ( )
14+where
Network/HTTP/Req/OAuth1.hsnew file+14
@@ -0,0 +1,14 @@
1+-- |
2+-- Module : Network.HTTP.Req.OAuth1
3+-- Copyright : © 2016 Mark Karpov
4+-- License : BSD 3 clause
5+--
6+-- Maintainer : Mark Karpov <markkarpov@openmailbox.org>
7+-- Stability : experimental
8+-- Portability : portable
9+--
10+-- Implementation of OAuth 1.0 signing.
11+
12+module Network.HTTP.Req.OAuth1
13+ ( )
14+where
req.cabal+3−1
@@ -69,10 +69,12 @@ library
69 , http-types >= 0.8 && < 10.0
69 , http-types >= 0.8 && < 10.0
70 , mtl >= 2.0 && < 3.0
70 , mtl >= 2.0 && < 3.0
71 , text >= 0.2 && < 1.3
71 , text >= 0.2 && < 1.3
72− , transformers >= 0.4 && < 0.6
72+ , transformers >= 0.4 && < 0.6
73 if !impl(ghc >= 8.0)
73 if !impl(ghc >= 8.0)
74 build-depends: semigroups == 0.18.*
74 build-depends: semigroups == 0.18.*
75 exposed-modules: Network.HTTP.Req
75 exposed-modules: Network.HTTP.Req
76+ other-modules: Network.HTTP.Req.AWS
77+ , Network.HTTP.Req.OAuth1
76 if flag(dev)
78 if flag(dev)
77 ghc-options: -Wall -Werror
79 ghc-options: -Wall -Werror
78 else
80 else