Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F101641923
PhabricatorAuthPasswordEngine.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
Wed, Feb 12, 09:08
Size
8 KB
Mime Type
text/x-php
Expires
Fri, Feb 14, 09:08 (2 d)
Engine
blob
Format
Raw Data
Handle
24206636
Attached To
rPH Phabricator
PhabricatorAuthPasswordEngine.php
View Options
<?php
final
class
PhabricatorAuthPasswordEngine
extends
Phobject
{
private
$viewer
;
private
$contentSource
;
private
$object
;
private
$passwordType
;
private
$upgradeHashers
=
true
;
public
function
setViewer
(
PhabricatorUser
$viewer
)
{
$this
->
viewer
=
$viewer
;
return
$this
;
}
public
function
getViewer
()
{
return
$this
->
viewer
;
}
public
function
setContentSource
(
PhabricatorContentSource
$content_source
)
{
$this
->
contentSource
=
$content_source
;
return
$this
;
}
public
function
getContentSource
()
{
return
$this
->
contentSource
;
}
public
function
setObject
(
PhabricatorAuthPasswordHashInterface
$object
)
{
$this
->
object
=
$object
;
return
$this
;
}
public
function
getObject
()
{
return
$this
->
object
;
}
public
function
setPasswordType
(
$password_type
)
{
$this
->
passwordType
=
$password_type
;
return
$this
;
}
public
function
getPasswordType
()
{
return
$this
->
passwordType
;
}
public
function
setUpgradeHashers
(
$upgrade_hashers
)
{
$this
->
upgradeHashers
=
$upgrade_hashers
;
return
$this
;
}
public
function
getUpgradeHashers
()
{
return
$this
->
upgradeHashers
;
}
public
function
checkNewPassword
(
PhutilOpaqueEnvelope
$password
,
PhutilOpaqueEnvelope
$confirm
,
$can_skip
=
false
)
{
$raw_password
=
$password
->
openEnvelope
();
if
(!
strlen
(
$raw_password
))
{
if
(
$can_skip
)
{
throw
new
PhabricatorAuthPasswordException
(
pht
(
'You must choose a password or skip this step.'
),
pht
(
'Required'
));
}
else
{
throw
new
PhabricatorAuthPasswordException
(
pht
(
'You must choose a password.'
),
pht
(
'Required'
));
}
}
$min_len
=
PhabricatorEnv
::
getEnvConfig
(
'account.minimum-password-length'
);
$min_len
=
(
int
)
$min_len
;
if
(
$min_len
)
{
if
(
strlen
(
$raw_password
)
<
$min_len
)
{
throw
new
PhabricatorAuthPasswordException
(
pht
(
'The selected password is too short. Passwords must be a minimum '
.
'of %s characters long.'
,
new
PhutilNumber
(
$min_len
)),
pht
(
'Too Short'
));
}
}
$raw_confirm
=
$confirm
->
openEnvelope
();
if
(!
strlen
(
$raw_confirm
))
{
throw
new
PhabricatorAuthPasswordException
(
pht
(
'You must confirm the selected password.'
),
null
,
pht
(
'Required'
));
}
if
(
$raw_password
!==
$raw_confirm
)
{
throw
new
PhabricatorAuthPasswordException
(
pht
(
'The password and confirmation do not match.'
),
pht
(
'Invalid'
),
pht
(
'Invalid'
));
}
if
(
PhabricatorCommonPasswords
::
isCommonPassword
(
$raw_password
))
{
throw
new
PhabricatorAuthPasswordException
(
pht
(
'The selected password is very weak: it is one of the most common '
.
'passwords in use. Choose a stronger password.'
),
pht
(
'Very Weak'
));
}
// If we're creating a brand new object (like registering a new user)
// and it does not have a PHID yet, it isn't possible for it to have any
// revoked passwords or colliding passwords either, so we can skip these
// checks.
if
(
$this
->
getObject
()->
getPHID
())
{
if
(
$this
->
isRevokedPassword
(
$password
))
{
throw
new
PhabricatorAuthPasswordException
(
pht
(
'The password you entered has been revoked. You can not reuse '
.
'a password which has been revoked. Choose a new password.'
),
pht
(
'Revoked'
));
}
if
(!
$this
->
isUniquePassword
(
$password
))
{
throw
new
PhabricatorAuthPasswordException
(
pht
(
'The password you entered is the same as another password '
.
'associated with your account. Each password must be unique.'
),
pht
(
'Not Unique'
));
}
}
}
public
function
isValidPassword
(
PhutilOpaqueEnvelope
$envelope
)
{
$this
->
requireSetup
();
$password_type
=
$this
->
getPasswordType
();
$passwords
=
$this
->
newQuery
()
->
withPasswordTypes
(
array
(
$password_type
))
->
withIsRevoked
(
false
)
->
execute
();
$matches
=
$this
->
getMatches
(
$envelope
,
$passwords
);
if
(!
$matches
)
{
return
false
;
}
if
(
$this
->
shouldUpgradeHashers
())
{
$this
->
upgradeHashers
(
$envelope
,
$matches
);
}
return
true
;
}
public
function
isUniquePassword
(
PhutilOpaqueEnvelope
$envelope
)
{
$this
->
requireSetup
();
$password_type
=
$this
->
getPasswordType
();
// To test that the password is unique, we're loading all active and
// revoked passwords for all roles for the given user, then throwing out
// the active passwords for the current role (so a password can't
// collide with itself).
// Note that two different objects can have the same password (say,
// users @alice and @bailey). We're only preventing @alice from using
// the same password for everything.
$passwords
=
$this
->
newQuery
()
->
execute
();
foreach
(
$passwords
as
$key
=>
$password
)
{
$same_type
=
(
$password
->
getPasswordType
()
===
$password_type
);
$is_active
=
!
$password
->
getIsRevoked
();
if
(
$same_type
&&
$is_active
)
{
unset
(
$passwords
[
$key
]);
}
}
$matches
=
$this
->
getMatches
(
$envelope
,
$passwords
);
return
!
$matches
;
}
public
function
isRevokedPassword
(
PhutilOpaqueEnvelope
$envelope
)
{
$this
->
requireSetup
();
// To test if a password is revoked, we're loading all revoked passwords
// across all roles for the given user. If a password was revoked in one
// role, you can't reuse it in a different role.
$passwords
=
$this
->
newQuery
()
->
withIsRevoked
(
true
)
->
execute
();
$matches
=
$this
->
getMatches
(
$envelope
,
$passwords
);
return
(
bool
)
$matches
;
}
private
function
requireSetup
()
{
if
(!
$this
->
getObject
())
{
throw
new
PhutilInvalidStateException
(
'setObject'
);
}
if
(!
$this
->
getPasswordType
())
{
throw
new
PhutilInvalidStateException
(
'setPasswordType'
);
}
if
(!
$this
->
getViewer
())
{
throw
new
PhutilInvalidStateException
(
'setViewer'
);
}
if
(
$this
->
shouldUpgradeHashers
())
{
if
(!
$this
->
getContentSource
())
{
throw
new
PhutilInvalidStateException
(
'setContentSource'
);
}
}
}
private
function
shouldUpgradeHashers
()
{
if
(!
$this
->
getUpgradeHashers
())
{
return
false
;
}
if
(
PhabricatorEnv
::
isReadOnly
())
{
// Don't try to upgrade hashers if we're in read-only mode, since we
// won't be able to write the new hash to the database.
return
false
;
}
return
true
;
}
private
function
newQuery
()
{
$viewer
=
$this
->
getViewer
();
$object
=
$this
->
getObject
();
$password_type
=
$this
->
getPasswordType
();
return
id
(
new
PhabricatorAuthPasswordQuery
())
->
setViewer
(
$viewer
)
->
withObjectPHIDs
(
array
(
$object
->
getPHID
()));
}
private
function
getMatches
(
PhutilOpaqueEnvelope
$envelope
,
array
$passwords
)
{
$object
=
$this
->
getObject
();
$matches
=
array
();
foreach
(
$passwords
as
$password
)
{
try
{
$is_match
=
$password
->
comparePassword
(
$envelope
,
$object
);
}
catch
(
PhabricatorPasswordHasherUnavailableException
$ex
)
{
$is_match
=
false
;
}
if
(
$is_match
)
{
$matches
[]
=
$password
;
}
}
return
$matches
;
}
private
function
upgradeHashers
(
PhutilOpaqueEnvelope
$envelope
,
array
$passwords
)
{
assert_instances_of
(
$passwords
,
'PhabricatorAuthPassword'
);
$need_upgrade
=
array
();
foreach
(
$passwords
as
$password
)
{
if
(!
$password
->
canUpgrade
())
{
continue
;
}
$need_upgrade
[]
=
$password
;
}
if
(!
$need_upgrade
)
{
return
;
}
$upgrade_type
=
PhabricatorAuthPasswordUpgradeTransaction
::
TRANSACTIONTYPE
;
$viewer
=
$this
->
getViewer
();
$content_source
=
$this
->
getContentSource
();
$unguarded
=
AphrontWriteGuard
::
beginScopedUnguardedWrites
();
foreach
(
$need_upgrade
as
$password
)
{
// This does the actual upgrade. We then apply a transaction to make
// the upgrade more visible and auditable.
$old_hasher
=
$password
->
getHasher
();
$password
->
upgradePasswordHasher
(
$envelope
,
$this
->
getObject
());
$new_hasher
=
$password
->
getHasher
();
// NOTE: We must save the change before applying transactions because
// the editor will reload the object to obtain a read lock.
$password
->
save
();
$xactions
=
array
();
$xactions
[]
=
$password
->
getApplicationTransactionTemplate
()
->
setTransactionType
(
$upgrade_type
)
->
setNewValue
(
$new_hasher
->
getHashName
());
$editor
=
$password
->
getApplicationTransactionEditor
()
->
setActor
(
$viewer
)
->
setContinueOnNoEffect
(
true
)
->
setContinueOnMissingFields
(
true
)
->
setContentSource
(
$content_source
)
->
setOldHasher
(
$old_hasher
)
->
applyTransactions
(
$password
,
$xactions
);
}
unset
(
$unguarded
);
}
}
Event Timeline
Log In to Comment