From f512806b55aefa3ae3e3c968c533cb37dd2a2c13 Mon Sep 17 00:00:00 2001 From: Brian Schroeder Date: Tue, 4 Aug 2015 19:38:36 -0400 Subject: [PATCH 1/3] Implement Nylas provider --- Yesod/Auth/OAuth2/Nylas.hs | 93 ++++++++++++++++++++++++++++++++++++++ yesod-auth-oauth2.cabal | 3 ++ 2 files changed, 96 insertions(+) create mode 100644 Yesod/Auth/OAuth2/Nylas.hs diff --git a/Yesod/Auth/OAuth2/Nylas.hs b/Yesod/Auth/OAuth2/Nylas.hs new file mode 100644 index 0000000..156676d --- /dev/null +++ b/Yesod/Auth/OAuth2/Nylas.hs @@ -0,0 +1,93 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE OverloadedStrings #-} + +module Yesod.Auth.OAuth2.Nylas + ( oauth2Nylas + , module Yesod.Auth.OAuth2 + ) where + +#if __GLASGOW_HASKELL__ < 710 +import Control.Applicative ((<$>), (<*>)) +#endif + +import Control.Monad (mzero) +import Control.Exception.Lifted (throwIO) +import Data.Aeson (FromJSON, Value(..), parseJSON, decode, (.:)) +import Data.Monoid ((<>)) +import Data.Text (Text) +import Data.Text.Encoding (encodeUtf8, decodeUtf8) +import Data.Vector ((!?)) +import Network.HTTP.Client (applyBasicAuth, parseUrl, httpLbs, responseStatus, responseBody) +import Network.HTTP.Conduit (Manager) + +import Yesod.Auth +import Yesod.Auth.OAuth2 + +import qualified Data.Text as T +import qualified Network.HTTP.Types as HT + +data NylasNamespace = NylasNamespace + { nylasNamespaceId :: Text + , nylasNamespaceAccountId :: Text + , nylasNamespaceEmailAddress :: Text + , nylasNamespaceName :: Text + , nylasNamespaceProvider :: Text + , nylasNamespaceOrganizationUnit :: Text + } + +instance FromJSON NylasNamespace where + parseJSON (Array singleton) = case singleton !? 0 of + Just (Object o) -> NylasNamespace + <$> o .: "id" + <*> o .: "account_id" + <*> o .: "email_address" + <*> o .: "name" + <*> o .: "provider" + <*> o .: "organization_unit" + _ -> mzero + parseJSON _ = mzero + +oauth2Nylas :: YesodAuth m + => Text -- ^ Client ID + -> Text -- ^ Client Secret + -> AuthPlugin m +oauth2Nylas clientId clientSecret = oauth2NylasScoped clientId clientSecret ["email"] + +oauth2NylasScoped :: YesodAuth m + => Text -- ^ Client ID + -> Text -- ^ Client Secret + -> [Text] -- ^ Scopes + -> AuthPlugin m +oauth2NylasScoped clientId clientSecret scopes = authOAuth2 "nylas" oauth fetchCreds + where + oauth = OAuth2 + { oauthClientId = encodeUtf8 clientId + , oauthClientSecret = encodeUtf8 clientSecret + , oauthOAuthorizeEndpoint = encodeUtf8 $ "https://api.nylas.com/oauth/authorize?scope=" <> T.intercalate "," scopes + , oauthAccessTokenEndpoint = "https://api.nylas.com/oauth/token" + , oauthCallback = Nothing + } + +fetchCreds :: Manager -> AccessToken -> IO (Creds a) +fetchCreds manager token = do + req <- applyBasicAuth (accessToken token) "" <$> parseUrl "https://api.nylas.com/n" + resp <- httpLbs req manager + if HT.statusIsSuccessful (responseStatus resp) + then case decode (responseBody resp) of + Just ns -> return $ toCreds ns token + Nothing -> throwIO $ InvalidProfileResponse "nylas" "failed to parse namespace" + else throwIO $ InvalidProfileResponse "nylas" "failed to get namespace" + +toCreds :: NylasNamespace -> AccessToken -> Creds a +toCreds ns token = Creds + { credsPlugin = "nylas" + , credsIdent = nylasNamespaceId ns + , credsExtra = + [ ("account_id", nylasNamespaceAccountId ns) + , ("email_address", nylasNamespaceEmailAddress ns) + , ("name", nylasNamespaceName ns) + , ("provider", nylasNamespaceProvider ns) + , ("organization_unit", nylasNamespaceOrganizationUnit ns) + , ("access_token", decodeUtf8 $ accessToken token) + ] + } diff --git a/yesod-auth-oauth2.cabal b/yesod-auth-oauth2.cabal index 99f96e5..4a7e42f 100644 --- a/yesod-auth-oauth2.cabal +++ b/yesod-auth-oauth2.cabal @@ -24,6 +24,7 @@ library build-depends: base >= 4.5 && < 5 , bytestring >= 0.9.1.4 + , http-client >= 0.4.0 && < 0.5 , http-conduit >= 2.0 && < 3.0 , http-types >= 0.8 && < 0.9 , aeson >= 0.6 && < 0.9 @@ -36,6 +37,7 @@ library , transformers >= 0.2.2 && < 0.5 , hoauth2 >= 0.4.7 && < 0.5 , lifted-base >= 0.2 && < 0.4 + , vector >= 0.10 && < 0.11 exposed-modules: Yesod.Auth.OAuth2 Yesod.Auth.OAuth2.Github @@ -44,6 +46,7 @@ library Yesod.Auth.OAuth2.Twitter Yesod.Auth.OAuth2.Upcase Yesod.Auth.OAuth2.EveOnline + Yesod.Auth.OAuth2.Nylas ghc-options: -Wall From a7edda4b3b2f8d99843805ba096ef65a95113033 Mon Sep 17 00:00:00 2001 From: Brian Schroeder Date: Tue, 11 Aug 2015 11:50:59 -0400 Subject: [PATCH 2/3] Wrap lines to 80 characters --- Yesod/Auth/OAuth2/Nylas.hs | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/Yesod/Auth/OAuth2/Nylas.hs b/Yesod/Auth/OAuth2/Nylas.hs index 156676d..6959317 100644 --- a/Yesod/Auth/OAuth2/Nylas.hs +++ b/Yesod/Auth/OAuth2/Nylas.hs @@ -17,11 +17,13 @@ import Data.Monoid ((<>)) import Data.Text (Text) import Data.Text.Encoding (encodeUtf8, decodeUtf8) import Data.Vector ((!?)) -import Network.HTTP.Client (applyBasicAuth, parseUrl, httpLbs, responseStatus, responseBody) +import Network.HTTP.Client (applyBasicAuth, parseUrl, httpLbs, responseStatus + , responseBody) import Network.HTTP.Conduit (Manager) - -import Yesod.Auth -import Yesod.Auth.OAuth2 +import Yesod.Auth (Creds(..), YesodAuth, AuthPlugin) +import Yesod.Auth.OAuth2 (OAuth2(..), AccessToken(..) + , YesodOAuth2Exception(InvalidProfileResponse) + , authOAuth2) import qualified Data.Text as T import qualified Network.HTTP.Types as HT @@ -51,32 +53,42 @@ oauth2Nylas :: YesodAuth m => Text -- ^ Client ID -> Text -- ^ Client Secret -> AuthPlugin m -oauth2Nylas clientId clientSecret = oauth2NylasScoped clientId clientSecret ["email"] +oauth2Nylas clientId clientSecret = + oauth2NylasScoped clientId clientSecret ["email"] oauth2NylasScoped :: YesodAuth m => Text -- ^ Client ID -> Text -- ^ Client Secret -> [Text] -- ^ Scopes -> AuthPlugin m -oauth2NylasScoped clientId clientSecret scopes = authOAuth2 "nylas" oauth fetchCreds +oauth2NylasScoped clientId clientSecret scopes = + authOAuth2 "nylas" oauth fetchCreds where + authorizeUrl = encodeUtf8 + $ "https://api.nylas.com/oauth/authorize?scope=" + <> T.intercalate "," scopes + tokenUrl = "https://api.nylas.com/oauth/token" oauth = OAuth2 { oauthClientId = encodeUtf8 clientId , oauthClientSecret = encodeUtf8 clientSecret - , oauthOAuthorizeEndpoint = encodeUtf8 $ "https://api.nylas.com/oauth/authorize?scope=" <> T.intercalate "," scopes - , oauthAccessTokenEndpoint = "https://api.nylas.com/oauth/token" + , oauthOAuthorizeEndpoint = authorizeUrl + , oauthAccessTokenEndpoint = tokenUrl , oauthCallback = Nothing } fetchCreds :: Manager -> AccessToken -> IO (Creds a) fetchCreds manager token = do - req <- applyBasicAuth (accessToken token) "" <$> parseUrl "https://api.nylas.com/n" + req <- authorize <$> parseUrl "https://api.nylas.com/n" resp <- httpLbs req manager if HT.statusIsSuccessful (responseStatus resp) then case decode (responseBody resp) of Just ns -> return $ toCreds ns token - Nothing -> throwIO $ InvalidProfileResponse "nylas" "failed to parse namespace" - else throwIO $ InvalidProfileResponse "nylas" "failed to get namespace" + Nothing -> throwIO parseFailure + else throwIO requestFailure + where + authorize = applyBasicAuth (accessToken token) "" + parseFailure = InvalidProfileResponse "nylas" "failed to parse namespace" + requestFailure = InvalidProfileResponse "nylas" "failed to get namespace" toCreds :: NylasNamespace -> AccessToken -> Creds a toCreds ns token = Creds From 6ce969176e8af6852888531d4c26f62083ebbd60 Mon Sep 17 00:00:00 2001 From: Brian Schroeder Date: Tue, 11 Aug 2015 12:03:22 -0400 Subject: [PATCH 3/3] Rearrange argument order --- Yesod/Auth/OAuth2/Nylas.hs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Yesod/Auth/OAuth2/Nylas.hs b/Yesod/Auth/OAuth2/Nylas.hs index 6959317..93a465e 100644 --- a/Yesod/Auth/OAuth2/Nylas.hs +++ b/Yesod/Auth/OAuth2/Nylas.hs @@ -53,15 +53,14 @@ oauth2Nylas :: YesodAuth m => Text -- ^ Client ID -> Text -- ^ Client Secret -> AuthPlugin m -oauth2Nylas clientId clientSecret = - oauth2NylasScoped clientId clientSecret ["email"] +oauth2Nylas = oauth2NylasScoped ["email"] oauth2NylasScoped :: YesodAuth m - => Text -- ^ Client ID - -> Text -- ^ Client Secret - -> [Text] -- ^ Scopes + => [Text] -- ^ Scopes + -> Text -- ^ Client ID + -> Text -- ^ Client Secret -> AuthPlugin m -oauth2NylasScoped clientId clientSecret scopes = +oauth2NylasScoped scopes clientId clientSecret = authOAuth2 "nylas" oauth fetchCreds where authorizeUrl = encodeUtf8