Skip to content

Commit d53b971

Browse files
author
John Wiegley
committed
Add support for OAuth token-based authentication
1 parent c7f243c commit d53b971

File tree

8 files changed

+41
-28
lines changed

8 files changed

+41
-28
lines changed

Github/Gists.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Github.Private
1313
-- | The list of all gists created by the user
1414
--
1515
-- > gists' (Just ("github-username", "github-password")) "mike-burns"
16-
gists' :: Maybe BasicAuth -> String -> IO (Either Error [Gist])
16+
gists' :: Maybe GithubAuth -> String -> IO (Either Error [Gist])
1717
gists' auth userName = githubGet' auth ["users", userName, "gists"]
1818

1919
-- | The list of all public gists created by the user.
@@ -25,7 +25,7 @@ gists = gists' Nothing
2525
-- | A specific gist, given its id, with authentication credentials
2626
--
2727
-- > gist' (Just ("github-username", "github-password")) "225074"
28-
gist' :: Maybe BasicAuth -> String -> IO (Either Error Gist)
28+
gist' :: Maybe GithubAuth -> String -> IO (Either Error Gist)
2929
gist' auth gistId = githubGet' auth ["gists", gistId]
3030

3131
-- | A specific gist, given its id.

Github/Issues.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ data IssueLimitation =
3737
-- number.'
3838
--
3939
-- > issue' (Just ("github-username", "github-password")) "thoughtbot" "paperclip" "462"
40-
issue' :: Maybe BasicAuth -> String -> String -> Int -> IO (Either Error Issue)
40+
issue' :: Maybe GithubAuth -> String -> String -> Int -> IO (Either Error Issue)
4141
issue' auth user repoName issueNumber =
4242
githubGet' auth ["repos", user, repoName, "issues", show issueNumber]
4343

@@ -52,7 +52,7 @@ issue = issue' Nothing
5252
-- restrictions as described in the @IssueLimitation@ data type.
5353
--
5454
-- > issuesForRepo' (Just ("github-username", "github-password")) "thoughtbot" "paperclip" [NoMilestone, OnlyClosed, Mentions "jyurek", Ascending]
55-
issuesForRepo' :: Maybe BasicAuth -> String -> String -> [IssueLimitation] -> IO (Either Error [Issue])
55+
issuesForRepo' :: Maybe GithubAuth -> String -> String -> [IssueLimitation] -> IO (Either Error [Issue])
5656
issuesForRepo' auth user repoName issueLimitations =
5757
githubGetWithQueryString'
5858
auth

Github/Organizations.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Github.Private
1313
-- | The public organizations for a user, given the user's login, with authorization
1414
--
1515
-- > publicOrganizationsFor' (Just ("github-username", "github-password")) "mike-burns"
16-
publicOrganizationsFor' :: Maybe BasicAuth -> String -> IO (Either Error [SimpleOrganization])
16+
publicOrganizationsFor' :: Maybe GithubAuth -> String -> IO (Either Error [SimpleOrganization])
1717
publicOrganizationsFor' auth userName = githubGet' auth ["users", userName, "orgs"]
1818

1919
-- | The public organizations for a user, given the user's login.
@@ -25,7 +25,7 @@ publicOrganizationsFor = publicOrganizationsFor' Nothing
2525
-- | Details on a public organization. Takes the organization's login.
2626
--
2727
-- > publicOrganization' (Just ("github-username", "github-password")) "thoughtbot"
28-
publicOrganization' :: Maybe BasicAuth -> String -> IO (Either Error Organization)
28+
publicOrganization' :: Maybe GithubAuth -> String -> IO (Either Error Organization)
2929
publicOrganization' auth organizationName = githubGet' auth ["orgs", organizationName]
3030

3131
-- | Details on a public organization. Takes the organization's login.

Github/Private.hs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
{-# LANGUAGE OverloadedStrings, StandaloneDeriving #-}
1+
{-# LANGUAGE OverloadedStrings, StandaloneDeriving, DeriveDataTypeable #-}
22
module Github.Private where
33

44
import Github.Data
55
import Data.Aeson
66
import Data.Attoparsec.ByteString.Lazy
7+
import Data.Data
78
import Control.Applicative
89
import Data.List
10+
import Data.CaseInsensitive (mk)
911
import qualified Data.ByteString.Char8 as BS
1012
import qualified Data.ByteString.Lazy.Char8 as LBS
1113
import Network.HTTP.Types (Method, Status(..))
@@ -14,10 +16,15 @@ import Data.Conduit (ResourceT)
1416
import qualified Control.Exception as E
1517
import Data.Maybe (fromMaybe)
1618

19+
-- | user/password for HTTP basic access authentication
20+
data GithubAuth = GithubBasicAuth BS.ByteString BS.ByteString
21+
| GithubOAuth String
22+
deriving (Show, Data, Typeable, Eq, Ord)
23+
1724
githubGet :: (FromJSON b, Show b) => [String] -> IO (Either Error b)
1825
githubGet = githubGet' Nothing
1926

20-
githubGet' :: (FromJSON b, Show b) => Maybe BasicAuth -> [String] -> IO (Either Error b)
27+
githubGet' :: (FromJSON b, Show b) => Maybe GithubAuth -> [String] -> IO (Either Error b)
2128
githubGet' auth paths =
2229
githubAPI (BS.pack "GET")
2330
(buildUrl paths)
@@ -27,21 +34,21 @@ githubGet' auth paths =
2734
githubGetWithQueryString :: (FromJSON b, Show b) => [String] -> String -> IO (Either Error b)
2835
githubGetWithQueryString = githubGetWithQueryString' Nothing
2936

30-
githubGetWithQueryString' :: (FromJSON b, Show b) => Maybe BasicAuth -> [String] -> String -> IO (Either Error b)
37+
githubGetWithQueryString' :: (FromJSON b, Show b) => Maybe GithubAuth -> [String] -> String -> IO (Either Error b)
3138
githubGetWithQueryString' auth paths queryString =
3239
githubAPI (BS.pack "GET")
3340
(buildUrl paths ++ "?" ++ queryString)
3441
auth
3542
(Nothing :: Maybe Value)
3643

37-
githubPost :: (ToJSON a, Show a, FromJSON b, Show b) => BasicAuth -> [String] -> a -> IO (Either Error b)
44+
githubPost :: (ToJSON a, Show a, FromJSON b, Show b) => GithubAuth -> [String] -> a -> IO (Either Error b)
3845
githubPost auth paths body =
3946
githubAPI (BS.pack "POST")
4047
(buildUrl paths)
4148
(Just auth)
4249
(Just body)
4350

44-
githubPatch :: (ToJSON a, Show a, FromJSON b, Show b) => BasicAuth -> [String] -> a -> IO (Either Error b)
51+
githubPatch :: (ToJSON a, Show a, FromJSON b, Show b) => GithubAuth -> [String] -> a -> IO (Either Error b)
4552
githubPatch auth paths body =
4653
githubAPI (BS.pack "PATCH")
4754
(buildUrl paths)
@@ -51,39 +58,44 @@ githubPatch auth paths body =
5158
buildUrl :: [String] -> String
5259
buildUrl paths = "https://api.github.com/" ++ intercalate "/" paths
5360

54-
githubAPI :: (ToJSON a, Show a, FromJSON b, Show b) => BS.ByteString -> String -> Maybe BasicAuth -> Maybe a -> IO (Either Error b)
61+
githubAPI :: (ToJSON a, Show a, FromJSON b, Show b) => BS.ByteString -> String -> Maybe GithubAuth -> Maybe a -> IO (Either Error b)
5562
githubAPI method url auth body = do
5663
result <- doHttps method url auth (Just encodedBody)
5764
return $ either (Left . HTTPConnectionError)
5865
(parseJson . responseBody)
5966
result
6067
where encodedBody = RequestBodyLBS $ encode $ toJSON body
6168

62-
-- | user/password for HTTP basic access authentication
63-
type BasicAuth = (BS.ByteString, BS.ByteString)
64-
65-
doHttps :: Method -> String -> Maybe BasicAuth -> Maybe (RequestBody (ResourceT IO)) -> IO (Either E.SomeException (Response LBS.ByteString))
69+
doHttps :: Method -> String -> Maybe GithubAuth -> Maybe (RequestBody (ResourceT IO)) -> IO (Either E.SomeException (Response LBS.ByteString))
6670
doHttps method url auth body = do
6771
let requestBody = fromMaybe (RequestBodyBS $ BS.pack "") body
72+
requestHeaders = maybe [] getOAuth auth
6873
(Just uri) = parseUrl url
6974
request = uri { method = method
7075
, secure = True
7176
, port = 443
7277
, requestBody = requestBody
78+
, requestHeaders = requestHeaders
7379
, checkStatus = successOrMissing
7480
}
75-
authRequest = maybe id (uncurry applyBasicAuth) auth request
81+
authRequest = getAuthRequest auth request
7682

7783
(getResponse authRequest >>= return . Right) `E.catches` [
7884
-- Re-throw AsyncException, otherwise execution will not terminate on
7985
-- SIGINT (ctrl-c). All AsyncExceptions are re-thrown (not just
8086
-- UserInterrupt) because all of them indicate severe conditions and
8187
-- should not occur during normal operation.
8288
E.Handler (\e -> E.throw (e :: E.AsyncException)),
83-
8489
E.Handler (\e -> (return . Left) (e :: E.SomeException))
8590
]
8691
where
92+
getAuthRequest (Just (GithubBasicAuth user pass)) = applyBasicAuth user pass
93+
getAuthRequest _ = id
94+
getBasicAuth (GithubBasicAuth user pass) = applyBasicAuth user pass
95+
getBasicAuth _ = id
96+
getOAuth (GithubOAuth token) = [(mk (BS.pack "Authorization"),
97+
BS.pack ("token " ++ token))]
98+
getOAuth _ = []
8799
getResponse request = withManager $ \manager -> httpLbs request manager
88100
successOrMissing s@(Status sci _) hs
89101
| (200 <= sci && sci < 300) || sci == 404 = Nothing

Github/PullRequests.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import Github.Private
1919
-- | With authentification
2020
--
2121
-- > pullRequestsFor' (Just ("github-username", "github-password")) "rails" "rails"
22-
pullRequestsFor' :: Maybe BasicAuth -> String -> String -> IO (Either Error [PullRequest])
22+
pullRequestsFor' :: Maybe GithubAuth -> String -> String -> IO (Either Error [PullRequest])
2323
pullRequestsFor' auth userName repoName =
2424
githubGet' auth ["repos", userName, repoName, "pulls"]
2525

@@ -34,7 +34,7 @@ pullRequestsFor = pullRequestsFor' Nothing
3434
-- | With authentification
3535
--
3636
-- > pullRequest' (Just ("github-username", "github-password")) "thoughtbot" "paperclip" 562
37-
pullRequest' :: Maybe BasicAuth -> String -> String -> Int -> IO (Either Error DetailedPullRequest)
37+
pullRequest' :: Maybe GithubAuth -> String -> String -> Int -> IO (Either Error DetailedPullRequest)
3838
pullRequest' auth userName repoName number =
3939
githubGet' auth ["repos", userName, repoName, "pulls", show number]
4040

@@ -50,7 +50,7 @@ pullRequest = pullRequest' Nothing
5050
-- | With authentification
5151
--
5252
-- > pullRequestCommits' (Just ("github-username", "github-password")) "thoughtbot" "paperclip" 688
53-
pullRequestCommits' :: Maybe BasicAuth -> String -> String -> Int -> IO (Either Error [Commit])
53+
pullRequestCommits' :: Maybe GithubAuth -> String -> String -> Int -> IO (Either Error [Commit])
5454
pullRequestCommits' auth userName repoName number =
5555
githubGet' auth ["repos", userName, repoName, "pulls", show number, "commits"]
5656

@@ -66,7 +66,7 @@ pullRequestCommits = pullRequestCommits' Nothing
6666
-- | With authentification
6767
--
6868
-- > pullRequestFiles' (Just ("github-username", "github-password")) "thoughtbot" "paperclip" 688
69-
pullRequestFiles' :: Maybe BasicAuth -> String -> String -> Int -> IO (Either Error [File])
69+
pullRequestFiles' :: Maybe GithubAuth -> String -> String -> Int -> IO (Either Error [File])
7070
pullRequestFiles' auth userName repoName number =
7171
githubGet' auth ["repos", userName, repoName, "pulls", show number, "files"]
7272
-- | The individual files that a pull request patches. Takes the repo owner and

Github/Repos.hs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ module Github.Repos (
1919
-- |
2020
-- Only authenticated users may modify repositories. Currently only
2121
-- /HTTP basic access authentication/ is implemented.
22-
,BasicAuth
22+
,GithubAuth(..)
2323

2424
-- ** Create
2525
,createRepo
@@ -160,14 +160,14 @@ newRepo name = NewRepo name Nothing Nothing Nothing Nothing Nothing Nothing
160160
-- Create a new repository.
161161
--
162162
-- > createRepo (user, password) (newRepo "some_repo") {newRepoHasIssues = Just False}
163-
createRepo :: BasicAuth -> NewRepo -> IO (Either Error Repo)
163+
createRepo :: GithubAuth -> NewRepo -> IO (Either Error Repo)
164164
createRepo auth = githubPost auth ["user", "repos"]
165165

166166
-- |
167167
-- Create a new repository for an organization.
168168
--
169169
-- > createOrganizationRepo (user, password) "thoughtbot" (newRepo "some_repo") {newRepoHasIssues = Just False}
170-
createOrganizationRepo :: BasicAuth -> String -> NewRepo -> IO (Either Error Repo)
170+
createOrganizationRepo :: GithubAuth -> String -> NewRepo -> IO (Either Error Repo)
171171
createOrganizationRepo auth org = githubPost auth ["orgs", org, "repos"]
172172

173173
data Edit = Edit {
@@ -205,7 +205,7 @@ instance ToJSON Edit where
205205
-- Edit an existing repository.
206206
--
207207
-- > editRepo (user, password) "some_user" "some_repo" def {editDescription = Just "some description"}
208-
editRepo :: BasicAuth
208+
editRepo :: GithubAuth
209209
-> String -- ^ owner
210210
-> String -- ^ repository name
211211
-> Edit
@@ -219,7 +219,7 @@ editRepo auth user repo body = githubPatch auth ["repos", user, repo] b
219219
-- Delete an existing repository.
220220
--
221221
-- > deleteRepo (user, password) "thoughtbot" "some_repo"
222-
deleteRepo :: BasicAuth
222+
deleteRepo :: GithubAuth
223223
-> String -- ^ owner
224224
-> String -- ^ repository name
225225
-> IO (Either Error ())

Github/Users.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Github.Private
1313
-- | With authentification
1414
--
1515
-- > userInfoFor' (Just ("github-username", "github-password")) "mike-burns"
16-
userInfoFor' :: Maybe BasicAuth -> String -> IO (Either Error DetailedOwner)
16+
userInfoFor' :: Maybe GithubAuth -> String -> IO (Either Error DetailedOwner)
1717
userInfoFor' auth userName = githubGet' auth ["users", userName]
1818

1919
-- | The information for a single user, by login name.

github.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ Library
143143
aeson == 0.6.1.0,
144144
attoparsec >= 0.10.3.0,
145145
bytestring,
146+
case-insensitive >= 0.4.0.4,
146147
containers,
147148
text,
148149
old-locale,

0 commit comments

Comments
 (0)