Drop the full products scope the Console does not accept

The Console's product APIs accept user, user:read and products:read only
(decision 21 names products:read). A token with just "products" was
refused everywhere despite its consent text.
This commit is contained in:
Maurice Preuß committed 2026-11-05 00:11:35 +01:00
1 parent d99512a359
commit e8432df997
5 files changed
+16 -11

No files matched your search

+2 -2
View File
@@ -2,8 +2,8 @@
## 3.1.0 (unreleased) ## 3.1.0 (unreleased)
- Add the scopes `products` and `products:read` (`Scope::PRODUCTS`, `Scope::PRODUCTS_READ`) for the - Add the scope `products:read` (`Scope::PRODUCTS_READ`) for read-only access to the product APIs
product APIs of the Anikeen Console. of the Anikeen Console.
- Add the reseller scopes `reseller-customers:read`, `reseller-customers`, `reseller-grants` and - Add the reseller scopes `reseller-customers:read`, `reseller-customers`, `reseller-grants` and
`reseller-orders` (`Scope::RESELLER_CUSTOMERS_READ`, `Scope::RESELLER_CUSTOMERS`, `reseller-orders` (`Scope::RESELLER_CUSTOMERS_READ`, `Scope::RESELLER_CUSTOMERS`,
`Scope::RESELLER_GRANTS`, `Scope::RESELLER_ORDERS`) for the reseller API of the Anikeen Console. `Scope::RESELLER_GRANTS`, `Scope::RESELLER_ORDERS`) for the reseller API of the Anikeen Console.
+3 -3
View File
@@ -172,14 +172,14 @@ these scopes for its product and reseller APIs:
| Scope | Constant | Grants | | Scope | Constant | Grants |
|-------|----------|--------| |-------|----------|--------|
| `products:read` | `Scope::PRODUCTS_READ` | view the products of your teams | | `products:read` | `Scope::PRODUCTS_READ` | view the products of your teams |
| `products` | `Scope::PRODUCTS` | full access to the products of your teams |
| `reseller-customers:read` | `Scope::RESELLER_CUSTOMERS_READ` | view a reseller team's customers and invitations | | `reseller-customers:read` | `Scope::RESELLER_CUSTOMERS_READ` | view a reseller team's customers and invitations |
| `reseller-customers` | `Scope::RESELLER_CUSTOMERS` | manage customers: invite, resend, remove logins (includes reading) | | `reseller-customers` | `Scope::RESELLER_CUSTOMERS` | manage customers: invite, resend, remove logins (includes reading) |
| `reseller-grants` | `Scope::RESELLER_GRANTS` | grant customers access to products and revoke it | | `reseller-grants` | `Scope::RESELLER_GRANTS` | grant customers access to products and revoke it |
| `reseller-orders` | `Scope::RESELLER_ORDERS` | order products for customers, charged right away | | `reseller-orders` | `Scope::RESELLER_ORDERS` | order products for customers, charged right away |
A full scope includes reading. `user` and `*` do not open the reseller endpoints: the token has to A full reseller scope includes reading. `user` and `*` do not open the reseller endpoints: the
carry the reseller scope itself. token has to carry the reseller scope itself. For the product APIs there is only `products:read`;
changing products needs `user`.
### Change the default access token / refresh token field name ### Change the default access token / refresh token field name
+3 -3
View File
@@ -172,14 +172,14 @@ these scopes for its product and reseller APIs:
| Scope | Constant | Grants | | Scope | Constant | Grants |
|-------|----------|--------| |-------|----------|--------|
| `products:read` | `Scope::PRODUCTS_READ` | view the products of your teams | | `products:read` | `Scope::PRODUCTS_READ` | view the products of your teams |
| `products` | `Scope::PRODUCTS` | full access to the products of your teams |
| `reseller-customers:read` | `Scope::RESELLER_CUSTOMERS_READ` | view a reseller team's customers and invitations | | `reseller-customers:read` | `Scope::RESELLER_CUSTOMERS_READ` | view a reseller team's customers and invitations |
| `reseller-customers` | `Scope::RESELLER_CUSTOMERS` | manage customers: invite, resend, remove logins (includes reading) | | `reseller-customers` | `Scope::RESELLER_CUSTOMERS` | manage customers: invite, resend, remove logins (includes reading) |
| `reseller-grants` | `Scope::RESELLER_GRANTS` | grant customers access to products and revoke it | | `reseller-grants` | `Scope::RESELLER_GRANTS` | grant customers access to products and revoke it |
| `reseller-orders` | `Scope::RESELLER_ORDERS` | order products for customers, charged right away | | `reseller-orders` | `Scope::RESELLER_ORDERS` | order products for customers, charged right away |
A full scope includes reading. `user` and `*` do not open the reseller endpoints: the token has to A full reseller scope includes reading. `user` and `*` do not open the reseller endpoints: the
carry the reseller scope itself. token has to carry the reseller scope itself. For the product APIs there is only `products:read`;
changing products needs `user`.
### Change the default access token / refresh token field name ### Change the default access token / refresh token field name
+2 -2
View File
@@ -36,8 +36,8 @@ class Scope
const SSH_KEYS = 'ssh-keys'; const SSH_KEYS = 'ssh-keys';
const SSH_KEYS_READ = 'ssh-keys:read'; const SSH_KEYS_READ = 'ssh-keys:read';
// Products of the Anikeen Console (servers, floating IPs, hosting, domains, zones, object storage). // Products of the Anikeen Console (servers, floating IPs, hosting, domains, zones, object storage),
const PRODUCTS = 'products'; // read-only. Changing products needs `user`; there is no full `products` scope.
const PRODUCTS_READ = 'products:read'; const PRODUCTS_READ = 'products:read';
// Reseller API of the Anikeen Console. A full scope includes reading; `user` and `*` do not // Reseller API of the Anikeen Console. A full scope includes reading; `user` and `*` do not
+6 -1
View File
@@ -11,7 +11,6 @@ class ScopeTest extends TestCase
public static function newScopes(): array public static function newScopes(): array
{ {
return [ return [
'products' => [Scope::PRODUCTS, 'products'],
'products:read' => [Scope::PRODUCTS_READ, 'products:read'], 'products:read' => [Scope::PRODUCTS_READ, 'products:read'],
'reseller-customers' => [Scope::RESELLER_CUSTOMERS, 'reseller-customers'], 'reseller-customers' => [Scope::RESELLER_CUSTOMERS, 'reseller-customers'],
'reseller-customers:read' => [Scope::RESELLER_CUSTOMERS_READ, 'reseller-customers:read'], 'reseller-customers:read' => [Scope::RESELLER_CUSTOMERS_READ, 'reseller-customers:read'],
@@ -26,6 +25,12 @@ class ScopeTest extends TestCase
$this->assertSame($expected, $constant); $this->assertSame($expected, $constant);
} }
public function test_there_is_no_full_products_scope_the_console_would_not_accept(): void
{
// the Console's product APIs accept `user`, `user:read` and `products:read` only
$this->assertNotContains('products', (new ReflectionClass(Scope::class))->getConstants());
}
public function test_every_scope_follows_the_resource_and_level_schema(): void public function test_every_scope_follows_the_resource_and_level_schema(): void
{ {
$scopes = array_values((new ReflectionClass(Scope::class))->getConstants()); $scopes = array_values((new ReflectionClass(Scope::class))->getConstants());