Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2892549
DiffusionSSHWorkflow.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
9 KB
Referenced Files
None
Subscribers
None
DiffusionSSHWorkflow.php
View Options
<?php
abstract
class
DiffusionSSHWorkflow
extends
PhabricatorSSHWorkflow
{
private
$args
;
private
$repository
;
private
$hasWriteAccess
;
private
$shouldProxy
;
private
$baseRequestPath
;
public
function
getRepository
(
)
{
if
(
!
$this
->
repository
)
{
throw
new
Exception
(
pht
(
'Repository is not available yet!'
)
)
;
}
return
$this
->
repository
;
}
private
function
setRepository
(
PhabricatorRepository
$repository
)
{
$this
->
repository
=
$repository
;
return
$this
;
}
public
function
getArgs
(
)
{
return
$this
->
args
;
}
public
function
getEnvironment
(
)
{
$env
=
array
(
DiffusionCommitHookEngine
::
ENV_USER
=>
$this
->
getSSHUser
(
)
->
getUsername
(
)
,
DiffusionCommitHookEngine
::
ENV_REMOTE_PROTOCOL
=>
'ssh'
,
)
;
$identifier
=
$this
->
getRequestIdentifier
(
)
;
if
(
$identifier
!==
null
)
{
$env
[
DiffusionCommitHookEngine
::
ENV_REQUEST
]
=
$identifier
;
}
$remote_address
=
$this
->
getSSHRemoteAddress
(
)
;
if
(
$remote_address
!==
null
)
{
$env
[
DiffusionCommitHookEngine
::
ENV_REMOTE_ADDRESS
]
=
$remote_address
;
}
return
$env
;
}
/**
* Identify and load the affected repository.
*/
abstract
protected
function
identifyRepository
(
)
;
abstract
protected
function
executeRepositoryOperations
(
)
;
abstract
protected
function
raiseWrongVCSException
(
PhabricatorRepository
$repository
)
;
protected
function
getBaseRequestPath
(
)
{
return
$this
->
baseRequestPath
;
}
protected
function
writeError
(
$message
)
{
$this
->
getErrorChannel
(
)
->
write
(
$message
)
;
return
$this
;
}
protected
function
getCurrentDeviceName
(
)
{
$device
=
AlmanacKeys
::
getLiveDevice
(
)
;
if
(
$device
)
{
return
$device
->
getName
(
)
;
}
return
php_uname
(
'n'
)
;
}
protected
function
shouldProxy
(
)
{
return
$this
->
shouldProxy
;
}
final
protected
function
getAlmanacServiceRefs
(
$for_write
)
{
$viewer
=
$this
->
getSSHUser
(
)
;
$repository
=
$this
->
getRepository
(
)
;
$is_cluster_request
=
$this
->
getIsClusterRequest
(
)
;
$refs
=
$repository
->
getAlmanacServiceRefs
(
$viewer
,
array
(
'neverProxy'
=>
$is_cluster_request
,
'protocols'
=>
array
(
'ssh'
,
)
,
'writable'
=>
$for_write
,
)
)
;
if
(
!
$refs
)
{
throw
new
Exception
(
pht
(
'Failed to generate an intracluster proxy URI even though this '
.
'request was routed as a proxy request.'
)
)
;
}
return
$refs
;
}
final
protected
function
getProxyCommand
(
$for_write
)
{
$refs
=
$this
->
getAlmanacServiceRefs
(
$for_write
)
;
$ref
=
head
(
$refs
)
;
return
$this
->
getProxyCommandForServiceRef
(
$ref
)
;
}
final
protected
function
getProxyCommandForServiceRef
(
DiffusionServiceRef
$ref
)
{
$uri
=
new
PhutilURI
(
$ref
->
getURI
(
)
)
;
$username
=
AlmanacKeys
::
getClusterSSHUser
(
)
;
if
(
$username
===
null
)
{
throw
new
Exception
(
pht
(
'Unable to determine the username to connect with when trying '
.
'to proxy an SSH request within the cluster.'
)
)
;
}
$port
=
$uri
->
getPort
(
)
;
$host
=
$uri
->
getDomain
(
)
;
$key_path
=
AlmanacKeys
::
getKeyPath
(
'device.key'
)
;
if
(
!
Filesystem
::
pathExists
(
$key_path
)
)
{
throw
new
Exception
(
pht
(
'Unable to proxy this SSH request within the cluster: this device '
.
'is not registered and has a missing device key (expected to '
.
'find key at "%s").'
,
$key_path
)
)
;
}
$options
=
array
(
)
;
$options
[
]
=
'-o'
;
$options
[
]
=
'StrictHostKeyChecking=no'
;
$options
[
]
=
'-o'
;
$options
[
]
=
'UserKnownHostsFile=/dev/null'
;
// This is suppressing "added <address> to the list of known hosts"
// messages, which are confusing and irrelevant when they arise from
// proxied requests. It might also be suppressing lots of useful errors,
// of course. Ideally, we would enforce host keys eventually. See T13121.
$options
[
]
=
'-o'
;
$options
[
]
=
'LogLevel=ERROR'
;
// NOTE: We prefix the command with "@username", which the far end of the
// connection will parse in order to act as the specified user. This
// behavior is only available to cluster requests signed by a trusted
// device key.
return
csprintf
(
'ssh %Ls -l %s -i %s -p %s %s -- %s %Ls'
,
$options
,
$username
,
$key_path
,
$port
,
$host
,
'@'
.
$this
->
getSSHUser
(
)
->
getUsername
(
)
,
$this
->
getOriginalArguments
(
)
)
;
}
final
public
function
execute
(
PhutilArgumentParser
$args
)
{
$this
->
args
=
$args
;
$viewer
=
$this
->
getSSHUser
(
)
;
$have_diffusion
=
PhabricatorApplication
::
isClassInstalledForViewer
(
'PhabricatorDiffusionApplication'
,
$viewer
)
;
if
(
!
$have_diffusion
)
{
throw
new
Exception
(
pht
(
'You do not have permission to access the Diffusion application, '
.
'so you can not interact with repositories over SSH.'
)
)
;
}
$repository
=
$this
->
identifyRepository
(
)
;
$this
->
setRepository
(
$repository
)
;
// NOTE: Here, we're just figuring out if this is a proxyable request to
// a clusterized repository or not. We don't (and can't) use the URI we get
// back directly.
// For example, we may get a read-only URI here but be handling a write
// request. We only care if we get back `null` (which means we should
// handle the request locally) or anything else (which means we should
// proxy it to an appropriate device).
$is_cluster_request
=
$this
->
getIsClusterRequest
(
)
;
$uri
=
$repository
->
getAlmanacServiceURI
(
$viewer
,
array
(
'neverProxy'
=>
$is_cluster_request
,
'protocols'
=>
array
(
'ssh'
,
)
,
)
)
;
$this
->
shouldProxy
=
(bool)
$uri
;
try
{
return
$this
->
executeRepositoryOperations
(
)
;
}
catch
(
Exception
$ex
)
{
$this
->
writeError
(
get_class
(
$ex
)
.
': '
.
$ex
->
getMessage
(
)
)
;
return
1
;
}
}
protected
function
loadRepositoryWithPath
(
$path
,
$vcs
)
{
$viewer
=
$this
->
getSSHUser
(
)
;
$info
=
PhabricatorRepository
::
parseRepositoryServicePath
(
$path
,
$vcs
)
;
if
(
$info
===
null
)
{
throw
new
Exception
(
pht
(
'Unrecognized repository path "%s". Expected a path like "%s", '
.
'"%s", or "%s".'
,
$path
,
'/diffusion/X/'
,
'/diffusion/123/'
,
'/source/thaumaturgy.git'
)
)
;
}
$identifier
=
$info
[
'identifier'
]
;
$base
=
$info
[
'base'
]
;
$this
->
baseRequestPath
=
$base
;
$repository
=
id
(
new
PhabricatorRepositoryQuery
(
)
)
->
setViewer
(
$viewer
)
->
withIdentifiers
(
array
(
$identifier
)
)
->
needURIs
(
true
)
->
executeOne
(
)
;
if
(
!
$repository
)
{
throw
new
Exception
(
pht
(
'No repository "%s" exists!'
,
$identifier
)
)
;
}
$is_cluster
=
$this
->
getIsClusterRequest
(
)
;
$protocol
=
PhabricatorRepositoryURI
::
BUILTIN_PROTOCOL_SSH
;
if
(
!
$repository
->
canServeProtocol
(
$protocol
,
false
,
$is_cluster
)
)
{
throw
new
Exception
(
pht
(
'This repository ("%s") is not available over SSH.'
,
$repository
->
getDisplayName
(
)
)
)
;
}
if
(
$repository
->
getVersionControlSystem
(
)
!=
$vcs
)
{
$this
->
raiseWrongVCSException
(
$repository
)
;
}
return
$repository
;
}
protected
function
requireWriteAccess
(
$protocol_command
=
null
)
{
if
(
$this
->
hasWriteAccess
===
true
)
{
return
;
}
$repository
=
$this
->
getRepository
(
)
;
$viewer
=
$this
->
getSSHUser
(
)
;
if
(
$viewer
->
isOmnipotent
(
)
)
{
throw
new
Exception
(
pht
(
'This request is authenticated as a cluster device, but is '
.
'performing a write. Writes must be performed with a real '
.
'user account.'
)
)
;
}
if
(
$repository
->
isReadOnly
(
)
)
{
throw
new
Exception
(
$repository
->
getReadOnlyMessageForDisplay
(
)
)
;
}
$protocol
=
PhabricatorRepositoryURI
::
BUILTIN_PROTOCOL_SSH
;
if
(
$repository
->
canServeProtocol
(
$protocol
,
true
)
)
{
$can_push
=
PhabricatorPolicyFilter
::
hasCapability
(
$viewer
,
$repository
,
DiffusionPushCapability
::
CAPABILITY
)
;
if
(
!
$can_push
)
{
throw
new
Exception
(
pht
(
'You do not have permission to push to this repository.'
)
)
;
}
}
else
{
if
(
$protocol_command
!==
null
)
{
throw
new
Exception
(
pht
(
'This repository is read-only over SSH (tried to execute '
.
'protocol command "%s").'
,
$protocol_command
)
)
;
}
else
{
throw
new
Exception
(
pht
(
'This repository is read-only over SSH.'
)
)
;
}
}
$this
->
hasWriteAccess
=
true
;
return
$this
->
hasWriteAccess
;
}
protected
function
shouldSkipReadSynchronization
(
)
{
$viewer
=
$this
->
getSSHUser
(
)
;
// Currently, the only case where devices interact over SSH without
// assuming user credentials is when synchronizing before a read. These
// synchronizing reads do not themselves need to be synchronized.
if
(
$viewer
->
isOmnipotent
(
)
)
{
return
true
;
}
return
false
;
}
protected
function
newPullEvent
(
)
{
$viewer
=
$this
->
getSSHUser
(
)
;
$repository
=
$this
->
getRepository
(
)
;
$remote_address
=
$this
->
getSSHRemoteAddress
(
)
;
return
id
(
new
PhabricatorRepositoryPullEvent
(
)
)
->
setEpoch
(
PhabricatorTime
::
getNow
(
)
)
->
setRemoteAddress
(
$remote_address
)
->
setRemoteProtocol
(
PhabricatorRepositoryPullEvent
::
PROTOCOL_SSH
)
->
setPullerPHID
(
$viewer
->
getPHID
(
)
)
->
setRepositoryPHID
(
$repository
->
getPHID
(
)
)
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sun, Jan 19, 16:59 (2 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126724
Default Alt Text
DiffusionSSHWorkflow.php (9 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment