mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-22 05:20:56 +01: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:
parent
db50d0fb11
commit
411331469a
7 changed files with 182 additions and 31 deletions
|
@ -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',
|
||||
|
|
|
@ -148,22 +148,42 @@ final class AlmanacDeviceEditor
|
|||
$message,
|
||||
$xaction);
|
||||
$errors[] = $error;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($xactions) {
|
||||
$duplicate = id(new AlmanacDeviceQuery())
|
||||
$other = id(new AlmanacDeviceQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withNames(array(last($xactions)->getNewValue()))
|
||||
->withNames(array($name))
|
||||
->executeOne();
|
||||
if ($duplicate && ($duplicate->getID() != $object->getID())) {
|
||||
if ($other && ($other->getID() != $object->getID())) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Not Unique'),
|
||||
pht('Almanac devices must have unique names.'),
|
||||
last($xactions));
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -140,22 +140,42 @@ final class AlmanacServiceEditor
|
|||
$message,
|
||||
$xaction);
|
||||
$errors[] = $error;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($xactions) {
|
||||
$duplicate = id(new AlmanacServiceQuery())
|
||||
$other = id(new AlmanacServiceQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withNames(array(last($xactions)->getNewValue()))
|
||||
->withNames(array($name))
|
||||
->executeOne();
|
||||
if ($duplicate && ($duplicate->getID() != $object->getID())) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacNamespaceQuery
|
||||
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||
extends AlmanacQuery {
|
||||
|
||||
private $ids;
|
||||
private $phids;
|
||||
|
|
|
@ -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 )------------------------------------------- */
|
||||
|
||||
|
|
|
@ -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
|
||||
==============================
|
||||
|
||||
|
|
Loading…
Reference in a new issue