packages feed

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 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)+