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 $callback 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.', self::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.', self::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 %s calls than %s calls.', self::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.', self::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.', self::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.', self::class, __FUNCTION__.'()', 'beginUnguardedWrites()')); } self::$allowUnguardedWrites = true; } }