google-oauth2-jwt (empty) → 0.1.0.0
raw patch · 4 files changed
+165/−0 lines, 4 filesdep +HsOpenSSLdep +RSAdep +basesetup-changed
Dependencies added: HsOpenSSL, RSA, base, base64-bytestring, bytestring, text, unix-time
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- google-oauth2-jwt.cabal +32/−0
- src/Network/Google/OAuth2/JWT.hs +101/−0
+ LICENSE view
@@ -0,0 +1,30 @@+google-oauth2-jwt - Copyright Michel Boucey (c) 2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Author Michel Boucey nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ google-oauth2-jwt.cabal view
@@ -0,0 +1,32 @@+name: google-oauth2-jwt+version: 0.1.0.0+synopsis: Get a signed JWT for Google Service Accounts+description: Please see README.md+homepage: https://github.com/MichelBoucey/google-oauth2-jwt+license: BSD3+license-file: LICENSE+author: Michel Boucey+maintainer: michel.boucey@gmail.com+copyright: Copyright (c) 2016 - Michel Boucey+category: Google+build-type: Simple+-- extra-source-files:+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: Network.Google.OAuth2.JWT+ build-depends: base >= 4.7 && < 5+ , base64-bytestring+ , bytestring+ , HsOpenSSL+ , RSA+ , text+ , unix-time+ default-language: Haskell2010+ GHC-options: -Wall++source-repository head+ type: git+ location: https://github.com/MichelBoucey/google-oauth2-jwt+
+ src/Network/Google/OAuth2/JWT.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Create a signed JWT needed to make the access token request+-- to gain access to Google APIs for server to server applications.+--+-- For all details : https://developers.google.com/identity/protocols/OAuth2ServiceAccount+--++module Network.Google.OAuth2.JWT where++import Codec.Crypto.RSA.Pure+import qualified Data.ByteString as B+import Data.ByteString.Base64.URL (encode)+import Data.ByteString.Lazy (fromStrict, toStrict)+import Data.Maybe (fromJust)+import Data.Monoid ((<>))+import qualified Data.Text as T+import Data.Text.Encoding+import Data.UnixTime (getUnixTime, utSeconds)+import Foreign.C.Types+import OpenSSL.EVP.PKey (toKeyPair)+import OpenSSL.PEM (PemPasswordSupply (PwNone),+ readPrivateKey)+import OpenSSL.RSA++type Scope = T.Text++type Email = T.Text++-- |Get the private key obtained from+-- the Google API Console from a PEM file.+fromPEMFile :: FilePath -> IO PrivateKey+fromPEMFile f = readFile f >>= fromPEMString++-- |Get the private key obtained from+-- the Google API Console from a PEM 'String'.+fromPEMString :: String -> IO PrivateKey+fromPEMString s =+ fromJust . toKeyPair <$> readPrivateKey s PwNone+ >>= \k -> return $ PrivateKey+ { private_pub =+ PublicKey { public_size = rsaSize k+ , public_n = rsaN k+ , public_e = rsaE k+ }+ , private_d = rsaD k+ , private_p = rsaP k+ , private_q = rsaQ k+ , private_dP = 0+ , private_dQ = 0+ , private_qinv = 0+ }++-- | Create the signed JWT ready for transmission+-- in the access token request as assertion value.+--+-- >grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=+--+getJWT :: Email+ -- ^ The email address of the service account.+ -> Maybe Email+ -- ^ The email address of the user for which the+ -- application is requesting delegated access.+ -> [Scope]+ -- ^The list of the permissions that the application requests.+ -> Maybe Int+ -- ^ Expiration time (maximun and default value is an hour, 3600).+ -> PrivateKey+ -- ^ The private key obtained from the Google API Console.+ -> IO (Either String B.ByteString)+ -- ^ Either an error message or a signed JWT.+getJWT iss msub scopes mexp privateKey = do+ let expt = fromIntegral $+ case mexp of+ Just e -> e+ Nothing -> 3600+ cs <- jwtClaimsSet+ (maybe T.empty (\s -> "\"sub\":\"" <> s <> "\",") msub) expt+ let i = jwtHeader <> "." <> cs+ return $ do+ if expt > 0 && expt <= 3600 then+ case rsassa_pkcs1_v1_5_sign hashSHA256 privateKey (fromStrict i) of+ Right s -> Right $ i <> "." <> (encode $ toStrict s)+ Left _ -> Left "RSAError"+ else Left "Bad expiration time"+ where+ jwtHeader = toJWT "{\"alg\":\"RS256\",\"typ\":\"JWT\"}"+ jwtClaimsSet s e = do+ (exp',iat') <-+ getUnixTime >>= \t ->+ return ( toText $ utSeconds t + CTime e+ , toText $ utSeconds t+ )+ return $ toJWT $+ "{\"iss\":\"" <> iss <> "\"," <> s <> "\"scope\":\""+ <> T.intercalate " " scopes <> "\",\"aud\":\"https://ww\+ \w.googleapis.com/oauth2/v4/token\",\"exp\":" <> exp'+ <> ",\"iat\":" <> iat' <> "}"+ toText = (T.pack . show)+ toJWT = (encode . encodeUtf8)+