git-lfs: Added support for http basic auth
This commit is contained in:
		
					parent
					
						
							
								45e5cc63b5
							
						
					
				
			
			
				commit
				
					
						6ae0a44c64
					
				
			
		
					 5 changed files with 80 additions and 26 deletions
				
			
		| 
						 | 
					@ -2,6 +2,7 @@ git-annex (7.20190913) UNRELEASED; urgency=medium
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  * Added --mimetype and --mimeencoding file matching options.
 | 
					  * Added --mimetype and --mimeencoding file matching options.
 | 
				
			||||||
  * Added --unlocked and --locked file matching options.
 | 
					  * Added --unlocked and --locked file matching options.
 | 
				
			||||||
 | 
					  * git-lfs: Added support for http basic auth.
 | 
				
			||||||
  * git-lfs: Only do endpoint discovery once when concurrency is enabled.
 | 
					  * git-lfs: Only do endpoint discovery once when concurrency is enabled.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 -- Joey Hess <id@joeyh.name>  Thu, 19 Sep 2019 11:11:19 -0400
 | 
					 -- Joey Hess <id@joeyh.name>  Thu, 19 Sep 2019 11:11:19 -0400
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,7 @@ import qualified Git
 | 
				
			||||||
import qualified Git.Types as Git
 | 
					import qualified Git.Types as Git
 | 
				
			||||||
import qualified Git.Url
 | 
					import qualified Git.Url
 | 
				
			||||||
import qualified Git.GCrypt
 | 
					import qualified Git.GCrypt
 | 
				
			||||||
 | 
					import qualified Git.Credential as Git
 | 
				
			||||||
import Config
 | 
					import Config
 | 
				
			||||||
import Config.Cost
 | 
					import Config.Cost
 | 
				
			||||||
import Remote.Helper.Special
 | 
					import Remote.Helper.Special
 | 
				
			||||||
| 
						 | 
					@ -29,6 +30,7 @@ import Annex.UUID
 | 
				
			||||||
import Crypto
 | 
					import Crypto
 | 
				
			||||||
import Backend.Hash
 | 
					import Backend.Hash
 | 
				
			||||||
import Utility.Hash
 | 
					import Utility.Hash
 | 
				
			||||||
 | 
					import Utility.Base64
 | 
				
			||||||
import Utility.SshHost
 | 
					import Utility.SshHost
 | 
				
			||||||
import Logs.RemoteState
 | 
					import Logs.RemoteState
 | 
				
			||||||
import qualified Utility.GitLFS as LFS
 | 
					import qualified Utility.GitLFS as LFS
 | 
				
			||||||
| 
						 | 
					@ -178,23 +180,17 @@ discoverLFSEndpoint :: LFS.TransferRequestOperation -> LFSHandle -> Annex (Maybe
 | 
				
			||||||
discoverLFSEndpoint tro h
 | 
					discoverLFSEndpoint tro h
 | 
				
			||||||
	| Git.repoIsSsh r = gossh
 | 
						| Git.repoIsSsh r = gossh
 | 
				
			||||||
	| Git.repoIsHttp r = gohttp
 | 
						| Git.repoIsHttp r = gohttp
 | 
				
			||||||
	| otherwise = do
 | 
						| otherwise = unsupportedurischeme
 | 
				
			||||||
		warning "git-lfs endpoint has unsupported URI scheme"
 | 
					 | 
				
			||||||
		return Nothing
 | 
					 | 
				
			||||||
  where
 | 
					  where
 | 
				
			||||||
  	r = remoteRepo h
 | 
					  	r = remoteRepo h
 | 
				
			||||||
	lfsrepouri = case Git.location r of
 | 
						lfsrepouri = case Git.location r of
 | 
				
			||||||
		Git.Url u -> u
 | 
							Git.Url u -> u
 | 
				
			||||||
		_ -> giveup $ "unsupported git-lfs remote location " ++ Git.repoLocation r
 | 
							_ -> giveup $ "unsupported git-lfs remote location " ++ Git.repoLocation r
 | 
				
			||||||
	gohttp = case tro of
 | 
						
 | 
				
			||||||
		LFS.RequestDownload -> return $ LFS.guessEndpoint lfsrepouri
 | 
						unsupportedurischeme = do
 | 
				
			||||||
		LFS.RequestUpload -> do
 | 
							warning "git-lfs endpoint has unsupported URI scheme"
 | 
				
			||||||
			-- git-lfs does support storing over http,
 | 
					 | 
				
			||||||
			-- but it would need prompting for http basic
 | 
					 | 
				
			||||||
			-- authentication each time git-annex discovered
 | 
					 | 
				
			||||||
			-- the endpoint.
 | 
					 | 
				
			||||||
			warning "Storing content in git-lfs currently needs a ssh repository url, not http."
 | 
					 | 
				
			||||||
		return Nothing
 | 
							return Nothing
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	gossh = case mkSshHost <$> Git.Url.hostuser r of
 | 
						gossh = case mkSshHost <$> Git.Url.hostuser r of
 | 
				
			||||||
		Nothing -> do
 | 
							Nothing -> do
 | 
				
			||||||
			warning "Unable to parse ssh url for git-lfs remote."
 | 
								warning "Unable to parse ssh url for git-lfs remote."
 | 
				
			||||||
| 
						 | 
					@ -228,6 +224,53 @@ discoverLFSEndpoint tro h
 | 
				
			||||||
						return Nothing
 | 
											return Nothing
 | 
				
			||||||
					Just endpoint -> return (Just endpoint)
 | 
										Just endpoint -> return (Just endpoint)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
 | 
						-- The endpoint may or may not need http basic authentication,
 | 
				
			||||||
 | 
						-- which involves using git-credential to prompt for the password.
 | 
				
			||||||
 | 
						--
 | 
				
			||||||
 | 
						-- To determine if it does, make a download or upload request to
 | 
				
			||||||
 | 
						-- it, not including any objects in the request, and see if
 | 
				
			||||||
 | 
						-- the server requests authentication.
 | 
				
			||||||
 | 
						gohttp = case LFS.guessEndpoint lfsrepouri of
 | 
				
			||||||
 | 
							Nothing -> unsupportedurischeme
 | 
				
			||||||
 | 
							Just endpoint@(LFS.URIEndpoint uri _) ->
 | 
				
			||||||
 | 
								case LFS.startTransferRequest (LFS.EndpointURI endpoint) transfernothing of
 | 
				
			||||||
 | 
									Nothing -> unsupportedurischeme
 | 
				
			||||||
 | 
									Just testreq -> flip catchNonAsync (const (returnendpoint endpoint)) $ do
 | 
				
			||||||
 | 
										resp <- makeSmallAPIRequest testreq
 | 
				
			||||||
 | 
										if needauth (responseStatus resp)
 | 
				
			||||||
 | 
											then do
 | 
				
			||||||
 | 
												cred <- prompt $ do
 | 
				
			||||||
 | 
													showOutput
 | 
				
			||||||
 | 
													inRepo $ Git.getUrlCredential (show uri)
 | 
				
			||||||
 | 
												let endpoint' = addbasicauth cred endpoint
 | 
				
			||||||
 | 
												case LFS.startTransferRequest (LFS.EndpointURI endpoint') transfernothing of
 | 
				
			||||||
 | 
													Nothing -> unsupportedurischeme
 | 
				
			||||||
 | 
													Just testreq' -> flip catchNonAsync (const (returnendpoint endpoint')) $ do
 | 
				
			||||||
 | 
														resp' <- makeSmallAPIRequest testreq'
 | 
				
			||||||
 | 
														inRepo $ if needauth (responseStatus resp')
 | 
				
			||||||
 | 
															then Git.rejectUrlCredential cred
 | 
				
			||||||
 | 
															else Git.approveUrlCredential cred
 | 
				
			||||||
 | 
														returnendpoint endpoint'
 | 
				
			||||||
 | 
											else returnendpoint endpoint
 | 
				
			||||||
 | 
						  where
 | 
				
			||||||
 | 
						  	transfernothing = LFS.TransferRequest
 | 
				
			||||||
 | 
								{ LFS.req_operation = tro
 | 
				
			||||||
 | 
								, LFS.req_transfers = [LFS.Basic]
 | 
				
			||||||
 | 
								, LFS.req_ref = Nothing
 | 
				
			||||||
 | 
								, LFS.req_objects = []
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							returnendpoint = return . Just . LFS.EndpointURI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							needauth status = status == unauthorized401
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							addbasicauth cred endpoint@(LFS.URIEndpoint uri httpheaders) =
 | 
				
			||||||
 | 
								case (Git.credentialUsername cred, Git.credentialPassword cred) of
 | 
				
			||||||
 | 
									(Just u, Just p) -> LFS.URIEndpoint uri $
 | 
				
			||||||
 | 
										M.insert (T.pack "Authorization") (T.pack (authheader u p)) httpheaders
 | 
				
			||||||
 | 
									_ -> endpoint
 | 
				
			||||||
 | 
							  where
 | 
				
			||||||
 | 
								authheader u p = "Basic " ++ toB64 (u ++ ":" ++ p)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- The endpoint is cached for later use.
 | 
					-- The endpoint is cached for later use.
 | 
				
			||||||
getLFSEndpoint :: LFS.TransferRequestOperation -> TVar LFSHandle -> Annex (Maybe LFS.Endpoint)
 | 
					getLFSEndpoint :: LFS.TransferRequestOperation -> TVar LFSHandle -> Annex (Maybe LFS.Endpoint)
 | 
				
			||||||
getLFSEndpoint tro hv = do
 | 
					getLFSEndpoint tro hv = do
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,8 @@ module Utility.GitLFS (
 | 
				
			||||||
	downloadOperationRequest,
 | 
						downloadOperationRequest,
 | 
				
			||||||
	uploadOperationRequests,
 | 
						uploadOperationRequests,
 | 
				
			||||||
	-- * endpoint discovery
 | 
						-- * endpoint discovery
 | 
				
			||||||
	Endpoint,
 | 
						Endpoint(..),
 | 
				
			||||||
 | 
						URIEndpoint(..),
 | 
				
			||||||
	guessEndpoint,
 | 
						guessEndpoint,
 | 
				
			||||||
	HostUser,
 | 
						HostUser,
 | 
				
			||||||
	sshDiscoverEndpointCommand,
 | 
						sshDiscoverEndpointCommand,
 | 
				
			||||||
| 
						 | 
					@ -63,6 +64,7 @@ import Data.Aeson.Types
 | 
				
			||||||
import GHC.Generics
 | 
					import GHC.Generics
 | 
				
			||||||
import Network.HTTP.Client
 | 
					import Network.HTTP.Client
 | 
				
			||||||
import Data.List
 | 
					import Data.List
 | 
				
			||||||
 | 
					import Data.Maybe
 | 
				
			||||||
import qualified Data.Map as M
 | 
					import qualified Data.Map as M
 | 
				
			||||||
import qualified Data.Text as T
 | 
					import qualified Data.Text as T
 | 
				
			||||||
import qualified Data.Text.Encoding as E
 | 
					import qualified Data.Text.Encoding as E
 | 
				
			||||||
| 
						 | 
					@ -279,10 +281,14 @@ type SHA256 = T.Text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- | The endpoint of a git-lfs server.
 | 
					-- | The endpoint of a git-lfs server.
 | 
				
			||||||
data Endpoint
 | 
					data Endpoint
 | 
				
			||||||
	= EndpointURI URI.URI
 | 
						= EndpointURI URIEndpoint
 | 
				
			||||||
	| EndpointDiscovered SshDiscoveryResponse
 | 
						| EndpointDiscovered SshDiscoveryResponse
 | 
				
			||||||
	deriving (Show)
 | 
						deriving (Show)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- | An endpoint that uses a URI, typically http or https.
 | 
				
			||||||
 | 
					data URIEndpoint = URIEndpoint URI.URI (M.Map HTTPHeader HTTPHeaderValue)
 | 
				
			||||||
 | 
						deriving (Show)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- | Command to run via ssh with to discover an endpoint. The FilePath is
 | 
					-- | Command to run via ssh with to discover an endpoint. The FilePath is
 | 
				
			||||||
-- the location of the git repository on the ssh server.
 | 
					-- the location of the git repository on the ssh server.
 | 
				
			||||||
--
 | 
					--
 | 
				
			||||||
| 
						 | 
					@ -305,13 +311,17 @@ parseSshDiscoverEndpointResponse resp = EndpointDiscovered <$> decode resp
 | 
				
			||||||
-- | Guesses the LFS endpoint from the http url of a git remote.
 | 
					-- | Guesses the LFS endpoint from the http url of a git remote.
 | 
				
			||||||
--
 | 
					--
 | 
				
			||||||
-- https://github.com/git-lfs/git-lfs/blob/master/docs/api/server-discovery.md
 | 
					-- https://github.com/git-lfs/git-lfs/blob/master/docs/api/server-discovery.md
 | 
				
			||||||
guessEndpoint :: URI.URI -> Maybe Endpoint
 | 
					--
 | 
				
			||||||
 | 
					-- Note that this will not include any authentication headers that may be
 | 
				
			||||||
 | 
					-- needed to access the endpoint.
 | 
				
			||||||
 | 
					guessEndpoint :: URI.URI -> Maybe URIEndpoint
 | 
				
			||||||
guessEndpoint uri = case URI.uriScheme uri of
 | 
					guessEndpoint uri = case URI.uriScheme uri of
 | 
				
			||||||
	"https:" -> Just endpoint
 | 
						"https:" -> Just endpoint
 | 
				
			||||||
	"http:" -> Just endpoint
 | 
						"http:" -> Just endpoint
 | 
				
			||||||
	_ -> Nothing
 | 
						_ -> Nothing
 | 
				
			||||||
  where
 | 
					  where
 | 
				
			||||||
	endpoint = EndpointURI $ uri
 | 
						endpoint = URIEndpoint uri' M.empty
 | 
				
			||||||
 | 
						uri' = uri
 | 
				
			||||||
		-- force https because the git-lfs protocol uses http
 | 
							-- force https because the git-lfs protocol uses http
 | 
				
			||||||
		-- basic auth tokens, which should not be exposed
 | 
							-- basic auth tokens, which should not be exposed
 | 
				
			||||||
		{ URI.uriScheme = "https:"
 | 
							{ URI.uriScheme = "https:"
 | 
				
			||||||
| 
						 | 
					@ -330,7 +340,7 @@ guessEndpoint uri = case URI.uriScheme uri of
 | 
				
			||||||
-- | Makes a Request that will start the process of making a transfer to or
 | 
					-- | Makes a Request that will start the process of making a transfer to or
 | 
				
			||||||
-- from the LFS endpoint.
 | 
					-- from the LFS endpoint.
 | 
				
			||||||
startTransferRequest :: Endpoint -> TransferRequest -> Maybe Request
 | 
					startTransferRequest :: Endpoint -> TransferRequest -> Maybe Request
 | 
				
			||||||
startTransferRequest (EndpointURI uri) tr = do
 | 
					startTransferRequest (EndpointURI (URIEndpoint uri headers)) tr = do
 | 
				
			||||||
	r <- requestFromURI uri
 | 
						r <- requestFromURI uri
 | 
				
			||||||
	return $ addLfsJsonHeaders $ r
 | 
						return $ addLfsJsonHeaders $ r
 | 
				
			||||||
		-- Since this uses the LFS batch API, it adds /objects/batch
 | 
							-- Since this uses the LFS batch API, it adds /objects/batch
 | 
				
			||||||
| 
						 | 
					@ -338,14 +348,14 @@ startTransferRequest (EndpointURI uri) tr = do
 | 
				
			||||||
		{ path = path r <> "/objects/batch"
 | 
							{ path = path r <> "/objects/batch"
 | 
				
			||||||
		, method = "POST"
 | 
							, method = "POST"
 | 
				
			||||||
		, requestBody = RequestBodyLBS (encode tr)
 | 
							, requestBody = RequestBodyLBS (encode tr)
 | 
				
			||||||
 | 
							, requestHeaders = requestHeaders r ++ headers'
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
startTransferRequest (EndpointDiscovered sr) tr = do
 | 
					 | 
				
			||||||
	uri <- URI.parseURI (T.unpack (endpoint_href sr))
 | 
					 | 
				
			||||||
	req <- startTransferRequest (EndpointURI uri) tr
 | 
					 | 
				
			||||||
	let headers = map convheader $ maybe [] M.toList $ endpoint_header sr
 | 
					 | 
				
			||||||
	return $ req { requestHeaders = requestHeaders req ++ headers }
 | 
					 | 
				
			||||||
  where
 | 
					  where
 | 
				
			||||||
	convheader (k, v) = (CI.mk (E.encodeUtf8 k), E.encodeUtf8 v)
 | 
						convheader (k, v) = (CI.mk (E.encodeUtf8 k), E.encodeUtf8 v)
 | 
				
			||||||
 | 
						headers' = map convheader (M.toList headers)
 | 
				
			||||||
 | 
					startTransferRequest (EndpointDiscovered sr) tr = do
 | 
				
			||||||
 | 
						uri <- URI.parseURI (T.unpack (endpoint_href sr))
 | 
				
			||||||
 | 
						startTransferRequest (EndpointURI (URIEndpoint uri (fromMaybe M.empty (endpoint_header sr)))) tr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- | "user@host" or just the hostname.
 | 
					-- | "user@host" or just the hostname.
 | 
				
			||||||
type HostUser = String
 | 
					type HostUser = String
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,11 +24,8 @@ because the protocol does not support deletion.
 | 
				
			||||||
A git-lfs special remote also functions as a regular git remote. You can
 | 
					A git-lfs special remote also functions as a regular git remote. You can
 | 
				
			||||||
use things like `git push` and `git pull` with it.
 | 
					use things like `git push` and `git pull` with it.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
To enable an existing git-lgs remote in another clone of the repository,
 | 
					To enable an existing git-lfs remote in another clone of the repository,
 | 
				
			||||||
you'll need to provide an url to it again. It's ok to provide a different
 | 
					you'll need to provide an url to it again. It's ok to provide a different
 | 
				
			||||||
url as long as it points to the same git-lfs repository.
 | 
					url as long as it points to the same git-lfs repository.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	git annex enableremote lfs url=https://github.com/yourname/yourrepo.git
 | 
						git annex enableremote lfs url=https://github.com/yourname/yourrepo.git
 | 
				
			||||||
 | 
					 | 
				
			||||||
Note that http urls currently only allow read access to the git-lfs
 | 
					 | 
				
			||||||
repository.
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,3 +7,6 @@ when accessing that repository over http.
 | 
				
			||||||
`git credential` provides a way to reuse git's authentication system,
 | 
					`git credential` provides a way to reuse git's authentication system,
 | 
				
			||||||
and would be more appropriate to use here than git-annex's own creds
 | 
					and would be more appropriate to use here than git-annex's own creds
 | 
				
			||||||
system for special remotes. --[[Joey]]
 | 
					system for special remotes. --[[Joey]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> [[done]] --[[Joey]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue