Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F79668920
AphrontWriteGuard.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
Tue, Aug 27, 16:02
Size
7 KB
Mime Type
text/x-php
Expires
Thu, Aug 29, 16:02 (1 d, 21 h)
Engine
blob
Format
Raw Data
Handle
20218498
Attached To
rPHU libphutil
AphrontWriteGuard.php
View Options
<?php
/**
* Guard writes against CSRF. The Aphront structure takes care of most of this
* for you, you just need to call:
*
* AphrontWriteGuard::willWrite();
*
* ...before executing a write against any new kind of storage engine. MySQL
* databases and the default file storage engines are already covered, but if
* you introduce new types of datastores make sure their writes are guarded. If
* you don't guard writes and make a mistake doing CSRF checks in a controller,
* a CSRF vulnerability can escape undetected.
*
* If you need to execute writes on a page which doesn't have CSRF tokens (for
* example, because you need to do logging), you can temporarily disable the
* write guard by calling:
*
* AphrontWriteGuard::beginUnguardedWrites();
* do_logging_write();
* AphrontWriteGuard::endUnguardedWrites();
*
* This is dangerous, because it disables the backup layer of CSRF protection
* this class provides. You should need this only very, very rarely.
*
* @task protect Protecting Writes
* @task disable Disabling Protection
* @task manage Managing Write Guards
* @task internal Internals
*/
final
class
AphrontWriteGuard
{
private
static
$instance
;
private
static
$allowUnguardedWrites
=
false
;
private
$callback
;
private
$allowDepth
=
0
;
/* -( Managing Write Guards )---------------------------------------------- */
/**
* Construct a new write guard for a request. Only one write guard may be
* active at a time. You must explicitly call @{method:dispose} when you are
* done with a write guard:
*
* $guard = new AphrontWriteGuard($callback);
* // ...
* $guard->dispose();
*
* Normally, you do not need to manage guards yourself -- the Aphront stack
* handles it for you.
*
* This class accepts a callback, which will be invoked when a write is
* attempted. The callback should validate the presence of a CSRF token in
* the request, or abort the request (e.g., by throwing an exception) if a
* valid token isn't present.
*
* @param callable CSRF callback.
* @return this
* @task manage
*/
public
function
__construct
(
$callback
)
{
if
(
self
::
$instance
)
{
throw
new
Exception
(
pht
(
'An %s already exists. Dispose of the previous guard '
.
'before creating a new one.'
,
__CLASS__
));
}
if
(
self
::
$allowUnguardedWrites
)
{
throw
new
Exception
(
pht
(
'An %s is being created in a context which permits '
.
'unguarded writes unconditionally. This is not allowed and '
.
'indicates a serious error.'
,
__CLASS__
));
}
$this
->
callback
=
$callback
;
self
::
$instance
=
$this
;
}
/**
* Dispose of the active write guard. You must call this method when you are
* done with a write guard. You do not normally need to call this yourself.
*
* @return void
* @task manage
*/
public
function
dispose
()
{
if
(!
self
::
$instance
)
{
throw
new
Exception
(
pht
(
'Attempting to dispose of write guard, but no write guard is active!'
));
}
if
(
$this
->
allowDepth
>
0
)
{
throw
new
Exception
(
pht
(
'Imbalanced %s: more % calls than %s calls.'
,
__CLASS__
,
'beginUnguardedWrites()'
,
'endUnguardedWrites()'
));
}
self
::
$instance
=
null
;
}
/**
* Determine if there is an active write guard.
*
* @return bool
* @task manage
*/
public
static
function
isGuardActive
()
{
return
(
bool
)
self
::
$instance
;
}
/**
* Return on instance of AphrontWriteGuard if it's active, or null
*
* @return AphrontWriteGuard|null
*/
public
static
function
getInstance
()
{
return
self
::
$instance
;
}
/* -( Protecting Writes )-------------------------------------------------- */
/**
* Declare intention to perform a write, validating that writes are allowed.
* You should call this method before executing a write whenever you implement
* a new storage engine where information can be permanently kept.
*
* Writes are permitted if:
*
* - The request has valid CSRF tokens.
* - Unguarded writes have been temporarily enabled by a call to
* @{method:beginUnguardedWrites}.
* - All write guarding has been disabled with
* @{method:allowDangerousUnguardedWrites}.
*
* If none of these conditions are true, this method will throw and prevent
* the write.
*
* @return void
* @task protect
*/
public
static
function
willWrite
()
{
if
(!
self
::
$instance
)
{
if
(!
self
::
$allowUnguardedWrites
)
{
throw
new
Exception
(
pht
(
'Unguarded write! There must be an active %s to perform writes.'
,
__CLASS__
));
}
else
{
// Unguarded writes are being allowed unconditionally.
return
;
}
}
$instance
=
self
::
$instance
;
if
(
$instance
->
allowDepth
==
0
)
{
call_user_func
(
$instance
->
callback
);
}
}
/* -( Disabling Write Protection )----------------------------------------- */
/**
* Enter a scope which permits unguarded writes. This works like
* @{method:beginUnguardedWrites} but returns an object which will end
* the unguarded write scope when its __destruct() method is called. This
* is useful to more easily handle exceptions correctly in unguarded write
* blocks:
*
* // Restores the guard even if do_logging() throws.
* function unguarded_scope() {
* $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
* do_logging();
* }
*
* @return AphrontScopedUnguardedWriteCapability Object which ends unguarded
* writes when it leaves scope.
* @task disable
*/
public
static
function
beginScopedUnguardedWrites
()
{
self
::
beginUnguardedWrites
();
return
new
AphrontScopedUnguardedWriteCapability
();
}
/**
* Begin a block which permits unguarded writes. You should use this very
* sparingly, and only for things like logging where CSRF is not a concern.
*
* You must pair every call to @{method:beginUnguardedWrites} with a call to
* @{method:endUnguardedWrites}:
*
* AphrontWriteGuard::beginUnguardedWrites();
* do_logging();
* AphrontWriteGuard::endUnguardedWrites();
*
* @return void
* @task disable
*/
public
static
function
beginUnguardedWrites
()
{
if
(!
self
::
$instance
)
{
return
;
}
self
::
$instance
->
allowDepth
++;
}
/**
* Declare that you have finished performing unguarded writes. You must
* call this exactly once for each call to @{method:beginUnguardedWrites}.
*
* @return void
* @task disable
*/
public
static
function
endUnguardedWrites
()
{
if
(!
self
::
$instance
)
{
return
;
}
if
(
self
::
$instance
->
allowDepth
<=
0
)
{
throw
new
Exception
(
pht
(
'Imbalanced %s: more %s calls than %s calls.'
,
__CLASS__
,
'endUnguardedWrites()'
,
'beginUnguardedWrites()'
));
}
self
::
$instance
->
allowDepth
--;
}
/**
* Allow execution of unguarded writes. This is ONLY appropriate for use in
* script contexts or other contexts where you are guaranteed to never be
* vulnerable to CSRF concerns. Calling this method is EXTREMELY DANGEROUS
* if you do not understand the consequences.
*
* If you need to perform unguarded writes on an otherwise guarded workflow
* which is vulnerable to CSRF, use @{method:beginUnguardedWrites}.
*
* @return void
* @task disable
*/
public
static
function
allowDangerousUnguardedWrites
(
$allow
)
{
if
(
self
::
$instance
)
{
throw
new
Exception
(
pht
(
'You can not unconditionally disable %s by calling %s while a write '
.
'guard is active. Use %s to temporarily allow unguarded writes.'
,
__CLASS__
,
__FUNCTION__
.
'()'
,
'beginUnguardedWrites()'
));
}
self
::
$allowUnguardedWrites
=
true
;
}
}
Event Timeline
Log In to Comment