Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F92685759
PhabricatorAuthChallenge.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Fri, Nov 22, 18:33
Size
6 KB
Mime Type
text/x-php
Expires
Sun, Nov 24, 18:33 (2 d)
Engine
blob
Format
Raw Data
Handle
22487323
Attached To
rPH Phabricator
PhabricatorAuthChallenge.php
View Options
<?php
final
class
PhabricatorAuthChallenge
extends
PhabricatorAuthDAO
implements
PhabricatorPolicyInterface
{
protected
$userPHID
;
protected
$factorPHID
;
protected
$sessionPHID
;
protected
$workflowKey
;
protected
$challengeKey
;
protected
$challengeTTL
;
protected
$responseDigest
;
protected
$responseTTL
;
protected
$isCompleted
;
protected
$properties
=
array
();
private
$responseToken
;
const
HTTPKEY
=
'__hisec.challenges__'
;
const
TOKEN_DIGEST_KEY
=
'auth.challenge.token'
;
public
static
function
initializeNewChallenge
()
{
return
id
(
new
self
())
->
setIsCompleted
(
0
);
}
public
static
function
newHTTPParametersFromChallenges
(
array
$challenges
)
{
assert_instances_of
(
$challenges
,
__CLASS__
);
$token_list
=
array
();
foreach
(
$challenges
as
$challenge
)
{
$token
=
$challenge
->
getResponseToken
();
if
(
$token
)
{
$token_list
[]
=
sprintf
(
'%s:%s'
,
$challenge
->
getPHID
(),
$token
->
openEnvelope
());
}
}
if
(!
$token_list
)
{
return
array
();
}
$token_list
=
implode
(
' '
,
$token_list
);
return
array
(
self
::
HTTPKEY
=>
$token_list
,
);
}
public
static
function
newChallengeResponsesFromRequest
(
array
$challenges
,
AphrontRequest
$request
)
{
assert_instances_of
(
$challenges
,
__CLASS__
);
$token_list
=
$request
->
getStr
(
self
::
HTTPKEY
);
$token_list
=
explode
(
' '
,
$token_list
);
$token_map
=
array
();
foreach
(
$token_list
as
$token_element
)
{
$token_element
=
trim
(
$token_element
,
' '
);
if
(!
strlen
(
$token_element
))
{
continue
;
}
// NOTE: This error message is intentionally not printing the token to
// avoid disclosing it. As a result, it isn't terribly useful, but no
// normal user should ever end up here.
if
(!
preg_match
(
'/^[^:]+:/'
,
$token_element
))
{
throw
new
Exception
(
pht
(
'This request included an improperly formatted MFA challenge '
.
'token and can not be processed.'
));
}
list
(
$phid
,
$token
)
=
explode
(
':'
,
$token_element
,
2
);
if
(
isset
(
$token_map
[
$phid
]))
{
throw
new
Exception
(
pht
(
'This request improperly specifies an MFA challenge token ("%s") '
.
'multiple times and can not be processed.'
,
$phid
));
}
$token_map
[
$phid
]
=
new
PhutilOpaqueEnvelope
(
$token
);
}
$challenges
=
mpull
(
$challenges
,
null
,
'getPHID'
);
$now
=
PhabricatorTime
::
getNow
();
foreach
(
$challenges
as
$challenge_phid
=>
$challenge
)
{
// If the response window has expired, don't attach the token.
if
(
$challenge
->
getResponseTTL
()
<
$now
)
{
continue
;
}
$token
=
idx
(
$token_map
,
$challenge_phid
);
if
(!
$token
)
{
continue
;
}
$challenge
->
setResponseToken
(
$token
);
}
}
protected
function
getConfiguration
()
{
return
array
(
self
::
CONFIG_SERIALIZATION
=>
array
(
'properties'
=>
self
::
SERIALIZATION_JSON
,
),
self
::
CONFIG_AUX_PHID
=>
true
,
self
::
CONFIG_COLUMN_SCHEMA
=>
array
(
'challengeKey'
=>
'text255'
,
'challengeTTL'
=>
'epoch'
,
'workflowKey'
=>
'text255'
,
'responseDigest'
=>
'text255?'
,
'responseTTL'
=>
'epoch?'
,
'isCompleted'
=>
'bool'
,
),
self
::
CONFIG_KEY_SCHEMA
=>
array
(
'key_issued'
=>
array
(
'columns'
=>
array
(
'userPHID'
,
'challengeTTL'
),
),
'key_collection'
=>
array
(
'columns'
=>
array
(
'challengeTTL'
),
),
),
)
+
parent
::
getConfiguration
();
}
public
function
getPHIDType
()
{
return
PhabricatorAuthChallengePHIDType
::
TYPECONST
;
}
public
function
getIsReusedChallenge
()
{
if
(
$this
->
getIsCompleted
())
{
return
true
;
}
if
(!
$this
->
getIsAnsweredChallenge
())
{
return
false
;
}
// If the challenge has been answered but the client has provided a token
// proving that they answered it, this is still a valid response.
if
(
$this
->
getResponseToken
())
{
return
false
;
}
return
true
;
}
public
function
getIsAnsweredChallenge
()
{
return
(
bool
)
$this
->
getResponseDigest
();
}
public
function
markChallengeAsAnswered
(
$ttl
)
{
$token
=
Filesystem
::
readRandomCharacters
(
32
);
$token
=
new
PhutilOpaqueEnvelope
(
$token
);
$unguarded
=
AphrontWriteGuard
::
beginScopedUnguardedWrites
();
$this
->
setResponseToken
(
$token
)
->
setResponseTTL
(
$ttl
)
->
save
();
unset
(
$unguarded
);
return
$this
;
}
public
function
markChallengeAsCompleted
()
{
return
$this
->
setIsCompleted
(
true
)
->
save
();
}
public
function
setResponseToken
(
PhutilOpaqueEnvelope
$token
)
{
if
(!
$this
->
getUserPHID
())
{
throw
new
PhutilInvalidStateException
(
'setUserPHID'
);
}
if
(
$this
->
responseToken
)
{
throw
new
Exception
(
pht
(
'This challenge already has a response token; you can not '
.
'set a new response token.'
));
}
if
(
preg_match
(
'/ /'
,
$token
->
openEnvelope
()))
{
throw
new
Exception
(
pht
(
'The response token for this challenge is invalid: response '
.
'tokens may not include spaces.'
));
}
$digest
=
PhabricatorHash
::
digestWithNamedKey
(
$token
->
openEnvelope
(),
self
::
TOKEN_DIGEST_KEY
);
if
(
$this
->
responseDigest
!==
null
)
{
if
(!
phutil_hashes_are_identical
(
$digest
,
$this
->
responseDigest
))
{
throw
new
Exception
(
pht
(
'Invalid response token for this challenge: token digest does '
.
'not match stored digest.'
));
}
}
else
{
$this
->
responseDigest
=
$digest
;
}
$this
->
responseToken
=
$token
;
return
$this
;
}
public
function
getResponseToken
()
{
return
$this
->
responseToken
;
}
public
function
setResponseDigest
(
$value
)
{
throw
new
Exception
(
pht
(
'You can not set the response digest for a challenge directly. '
.
'Instead, set a response token. A response digest will be computed '
.
'automatically.'
));
}
public
function
setProperty
(
$key
,
$value
)
{
$this
->
properties
[
$key
]
=
$value
;
return
$this
;
}
public
function
getProperty
(
$key
,
$default
=
null
)
{
return
$this
->
properties
[
$key
];
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public
function
getCapabilities
()
{
return
array
(
PhabricatorPolicyCapability
::
CAN_VIEW
,
);
}
public
function
getPolicy
(
$capability
)
{
return
PhabricatorPolicies
::
POLICY_NOONE
;
}
public
function
hasAutomaticCapability
(
$capability
,
PhabricatorUser
$viewer
)
{
return
(
$viewer
->
getPHID
()
===
$this
->
getUserPHID
());
}
}
Event Timeline
Log In to Comment