1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-09-20 09:18:48 +02:00

Apply namespace locking rules in Almanac

Summary:
Ref T10246. Ref T6741.

When you have a namespace like "phacility.net", require users creating services and devices within it to have edit permission on the namespace.

This primarily allows us to lock down future device names in the cluster, so instances can't break themselves once they get access to Almanac.

Test Plan:
  - Configured a `phacility.net` namespace, locked myself out of it.
  - Could not create new `stuff.phacility.net` services/devices.
  - Could still edit existing devices I had permission for.
  - Configured a `free.phacility.net` namespace with more liberal policies.
  - Could create `me.free.phacility.net`.
  - Still could not create `other.phacility.net`.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T6741, T10246

Differential Revision: https://secure.phabricator.com/D15325
This commit is contained in:
epriestley 2016-02-21 14:34:03 -08:00
parent db50d0fb11
commit 411331469a
7 changed files with 182 additions and 31 deletions

View file

@ -4079,7 +4079,7 @@ phutil_register_library_map(array(
'AlmanacNamespaceListController' => 'AlmanacNamespaceController',
'AlmanacNamespaceNameNgrams' => 'PhabricatorSearchNgrams',
'AlmanacNamespacePHIDType' => 'PhabricatorPHIDType',
'AlmanacNamespaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'AlmanacNamespaceQuery' => 'AlmanacQuery',
'AlmanacNamespaceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'AlmanacNamespaceTransaction' => 'PhabricatorApplicationTransaction',
'AlmanacNamespaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',

View file

@ -148,22 +148,42 @@ final class AlmanacDeviceEditor
$message,
$xaction);
$errors[] = $error;
continue;
}
}
}
if ($xactions) {
$duplicate = id(new AlmanacDeviceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withNames(array(last($xactions)->getNewValue()))
->executeOne();
if ($duplicate && ($duplicate->getID() != $object->getID())) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Not Unique'),
pht('Almanac devices must have unique names.'),
last($xactions));
$errors[] = $error;
$other = id(new AlmanacDeviceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withNames(array($name))
->executeOne();
if ($other && ($other->getID() != $object->getID())) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Not Unique'),
pht('Almanac devices must have unique names.'),
$xaction);
$errors[] = $error;
continue;
}
if ($name === $object->getName()) {
continue;
}
$namespace = AlmanacNamespace::loadRestrictedNamespace(
$this->getActor(),
$name);
if ($namespace) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Restricted'),
pht(
'You do not have permission to create Almanac devices '.
'within the "%s" namespace.',
$namespace->getName()),
$xaction);
$errors[] = $error;
continue;
}
}
}

View file

@ -123,7 +123,7 @@ final class AlmanacNamespaceEditor
if ($other && ($other->getID() != $object->getID())) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht('Not Unique'),
pht(
'The namespace name "%s" is already in use by another '.
'namespace. Each namespace must have a unique name.',
@ -132,6 +132,26 @@ final class AlmanacNamespaceEditor
$errors[] = $error;
continue;
}
if ($name === $object->getName()) {
continue;
}
$namespace = AlmanacNamespace::loadRestrictedNamespace(
$this->getActor(),
$name);
if ($namespace) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Restricted'),
pht(
'You do not have permission to create Almanac namespaces '.
'within the "%s" namespace.',
$namespace->getName()),
$xaction);
$errors[] = $error;
continue;
}
}
}

View file

@ -140,22 +140,42 @@ final class AlmanacServiceEditor
$message,
$xaction);
$errors[] = $error;
continue;
}
}
}
if ($xactions) {
$duplicate = id(new AlmanacServiceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withNames(array(last($xactions)->getNewValue()))
->executeOne();
if ($duplicate && ($duplicate->getID() != $object->getID())) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Not Unique'),
pht('Almanac services must have unique names.'),
last($xactions));
$errors[] = $error;
$other = id(new AlmanacServiceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withNames(array($name))
->executeOne();
if ($other && ($other->getID() != $object->getID())) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Not Unique'),
pht('Almanac services must have unique names.'),
last($xactions));
$errors[] = $error;
continue;
}
if ($name === $object->getName()) {
continue;
}
$namespace = AlmanacNamespace::loadRestrictedNamespace(
$this->getActor(),
$name);
if ($namespace) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Restricted'),
pht(
'You do not have permission to create Almanac services '.
'within the "%s" namespace.',
$namespace->getName()),
$xaction);
$errors[] = $error;
continue;
}
}
}

View file

@ -1,7 +1,7 @@
<?php
final class AlmanacNamespaceQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
extends AlmanacQuery {
private $ids;
private $phids;

View file

@ -68,6 +68,51 @@ final class AlmanacNamespace
return '/almanac/namespace/view/'.$this->getName().'/';
}
public function getNameLength() {
return strlen($this->getName());
}
/**
* Load the namespace which prevents use of an Almanac name, if one exists.
*/
public static function loadRestrictedNamespace(
PhabricatorUser $viewer,
$name) {
// For a name like "x.y.z", produce a list of controlling namespaces like
// ("z", "y.x", "x.y.z").
$names = array();
$parts = explode('.', $name);
for ($ii = 0; $ii < count($parts); $ii++) {
$names[] = implode('.', array_slice($parts, -($ii + 1)));
}
// Load all the possible controlling namespaces.
$namespaces = id(new AlmanacNamespaceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withNames($names)
->execute();
if (!$namespaces) {
return null;
}
// Find the "nearest" (longest) namespace that exists. If both
// "sub.domain.com" and "domain.com" exist, we only care about the policy
// on the former.
$namespaces = msort($namespaces, 'getNameLength');
$namespace = last($namespaces);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$namespace,
PhabricatorPolicyCapability::CAN_EDIT);
if ($can_edit) {
return null;
}
return $namespace;
}
/* -( AlmanacPropertyInterface )------------------------------------------- */

View file

@ -133,6 +133,52 @@ controls who can create services with names like `a.mycompany.com` and
`b.mycompany.com`.
Namespaces
==========
Almanac namespaces allow you to control who can create services and devices
with certain names.
If you keep a list of cattle as devices with names like
`cow001.herd.myranch.com`, `cow002.herd.myranch.moo`, you might have some
applications which query for all devices in `*.herd.myranch.moo`, and thus
want to limit who can create devices there in order to prevent mistakes.
If a namespace like `herd.myranch.moo` exists, users must have permission to
edit the namespace in order to create new services, devices, or namespaces
within it. For example, a user can not create `cow003.herd.myranch.moo` if
they do not have edit permission on the `herd.myranch.moo` namespace.
When you try to create a `cow003.herd.myranch.moo` service (or rename an
existing service to have that name), Almanac looks for these namespaces, then
checks the policy of the first one it finds:
| Namespace |
|----|-----
| `cow003.herd.ranch.moo` | //"Nearest" namespace, considered first.//
| `herd.ranch.moo` | |
| `ranch.moo` | |
| `moo` | //"Farthest" namespace, considered last.//
Note that namespaces treat names as lists of domain parts, not as strict
substrings, so the namespace `herd.myranch.moo` does not prevent
someone from creating `goatherd.myranch.moo` or `goat001.goatherd.myranch.moo`.
The name `goatherd.myranch.moo` is not part of the `herd.myranch.moo` namespace
because the initial subdomain differs.
If a name belongs to multiple namespaces, the policy of the nearest namespace
is controlling. For example, if `myranch.moo` has a very restrictive edit
policy but `shed.myranch.moo` has a more open one, users can create devices and
services like `rake.shed.myranch.moo` as long as they can pass the policy check
for `shed.myranch.moo`, even if they do not have permission under the policy
for `myranch.moo`.
Users can edit services and devices within a namespace if they have edit
permission on the service or device itself, as long as they don't try to rename
the service or device to move it into a namespace they don't have permission
to access.
Locking and Unlocking Services
==============================