Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ public/apc.php
.claude/
.nvmrc
.codegraph
docs/
Original file line number Diff line number Diff line change
Expand Up @@ -3205,11 +3205,11 @@ public function deleteSocialNetwork($summit_id, $sponsor_id, $social_network_id)
});
}

// Extra Questions
// Sponsor Extra Questions

#[OA\Get(
path: "/api/v1/summits/{id}/sponsors/{sponsor_id}/extra-questions",
description: "required-groups " . IGroup::SuperAdmins . ", " . IGroup::Administrators . ", " . IGroup::SummitAdministrators . ", " . IGroup::Sponsors,
description: "required-groups " . IGroup::SuperAdmins . ", " . IGroup::Administrators . ", " . IGroup::SummitAdministrators . ", " . IGroup::Sponsors . ", " . IGroup::SponsorExternalUsers,
summary: 'Read Sponsor Extra Questions',
operationId: 'getSponsorExtraQuestions',
tags: ['Sponsors'],
Expand All @@ -3219,13 +3219,15 @@ public function deleteSocialNetwork($summit_id, $sponsor_id, $social_network_id)
IGroup::Administrators,
IGroup::SummitAdministrators,
IGroup::Sponsors,
IGroup::SponsorExternalUsers,
]
],
security: [
[
'summit_sponsor_oauth2' => [
SummitScopes::ReadSummitData,
SummitScopes::ReadAllSummitData,
SummitScopes::ReadSponsorExtraQuestions,
]
]
],
Expand Down Expand Up @@ -3365,6 +3367,7 @@ function ($page, $per_page, $filter, $order, $applyExtraFilters) {
'summit_sponsor_oauth2' => [
SummitScopes::ReadSummitData,
SummitScopes::ReadAllSummitData,
SummitScopes::ReadSponsorExtraQuestions,
]
]
],
Expand Down Expand Up @@ -3421,6 +3424,7 @@ public function getMetadata($summit_id)
[
'summit_sponsor_oauth2' => [
SummitScopes::WriteSummitData,
SummitScopes::WriteSponsorExtraQuestions,
]
]
],
Expand Down Expand Up @@ -3510,6 +3514,7 @@ public function addExtraQuestion($summit_id, $sponsor_id)
'summit_sponsor_oauth2' => [
SummitScopes::ReadSummitData,
SummitScopes::ReadAllSummitData,
SummitScopes::ReadSponsorExtraQuestions,
]
]
],
Expand Down Expand Up @@ -3600,6 +3605,7 @@ public function getExtraQuestion($summit_id, $sponsor_id, $extra_question_id)
[
'summit_sponsor_oauth2' => [
SummitScopes::WriteSummitData,
SummitScopes::WriteSponsorExtraQuestions,
]
]
],
Expand Down Expand Up @@ -3695,6 +3701,7 @@ public function updateExtraQuestion($summit_id, $sponsor_id, $extra_question_id)
[
'summit_sponsor_oauth2' => [
SummitScopes::WriteSummitData,
SummitScopes::WriteSponsorExtraQuestions,
]
]
],
Expand Down Expand Up @@ -3780,6 +3787,7 @@ public function deleteExtraQuestion($summit_id, $sponsor_id, $extra_question_id)
[
'summit_sponsor_oauth2' => [
SummitScopes::WriteSummitData,
SummitScopes::WriteSponsorExtraQuestions,
]
]
],
Expand Down Expand Up @@ -3879,6 +3887,7 @@ function ($payload, $summit, $sponsor_id, $question_id) {
[
'summit_sponsor_oauth2' => [
SummitScopes::WriteSummitData,
SummitScopes::WriteSponsorExtraQuestions,
]
]
],
Expand Down Expand Up @@ -3987,6 +3996,7 @@ function ($value_id, $payload, $summit, $sponsor_id, $extra_question_id) {
[
'summit_sponsor_oauth2' => [
SummitScopes::WriteSummitData,
SummitScopes::WriteSponsorExtraQuestions,
]
]
],
Expand Down
3 changes: 3 additions & 0 deletions app/Security/SummitScopes.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,8 @@ final class SummitScopes

const WriteSummitsConfirmExternalOrders = SCOPE_BASE_REALM.'/summits/confirm-external-orders';
const ReadSummitsConfirmExternalOrders = SCOPE_BASE_REALM.'/summits/read-external-orders';

const WriteSponsorExtraQuestions = SCOPE_BASE_REALM.'/summits/sponsors/extra-questions/write';
const ReadSponsorExtraQuestions = SCOPE_BASE_REALM.'/summits/sponsors/extra-questions/read';
}

2 changes: 2 additions & 0 deletions app/Swagger/Security/SponsorOAuth2Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
SummitScopes::ReadSummitData => 'Read Summit Sponsor Data',
SummitScopes::ReadAllSummitData => 'Read All Summit Sponsor Data',
SummitScopes::WriteSummitData => 'Write Summit Sponsor Data',
SummitScopes::ReadSponsorExtraQuestions => 'Read Summit Sponsor Extra Questions Data',
SummitScopes::WriteSponsorExtraQuestions => 'Write Summit Sponsor Extra Questions Data',
],
),
],
Expand Down
234 changes: 234 additions & 0 deletions database/migrations/config/APIEndpointsMigrationHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
<?php namespace Database\Migrations\Config;
/**
* Copyright 2026 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

/**
* Reusable SQL template helpers for config entity manager migrations.
*
* Provides idempotent INSERT and DELETE templates for api_endpoints, api_scopes,
* endpoint_api_scopes, and endpoint_api_authz_groups tables.
*
* Usage:
* final class VersionXXX extends AbstractMigration
* {
* use APIEndpointsMigrationHelper;
*
* public function up(Schema $schema): void
* {
* $this->addSql($this->insertApiScope('summits', $scopeName, $desc, $desc));
* $this->addSql($this->insertEndpointScope('summits', $endpointName, $scopeName));
* $this->addSql($this->insertEndpointAuthzGroup('summits', $endpointName, $groupSlug));
* }
* }
*/
trait APIEndpointsMigrationHelper
{
/**
* Generate idempotent INSERT for api_endpoints table.
*
* @param string $apiName API identifier (e.g., 'summits')
* @param string $endpointName Endpoint identifier (e.g., 'get-sponsor-extra-questions')
* @param string $route Route pattern (e.g., '/api/v1/summits/{id}/sponsors/{sponsor_id}/extra-questions')
* @param string $httpMethod Plain HTTP method string (e.g., 'GET', 'POST', 'PUT', 'DELETE')
* @param bool $active Whether the endpoint is active (default: true)
* @param bool $allowCors Whether to allow CORS (default: true, matches seedApiEndpoints behavior)
* @param bool $allowCredentials Whether to allow credentials (default: true, matches seedApiEndpoints behavior)
* @return string SQL INSERT statement
*/
protected function insertEndpoint(
string $apiName,
string $endpointName,
string $route,
string $httpMethod,
bool $active = true,
bool $allowCors = true,
bool $allowCredentials = true
): string {
$activeInt = $active ? 1 : 0;
$corsInt = $allowCors ? 1 : 0;
$credentialsInt = $allowCredentials ? 1 : 0;

return <<<SQL
INSERT INTO api_endpoints (api_id, name, route, http_method, active, allow_cors, allow_credentials, created_at, updated_at)
SELECT a.id, '{$endpointName}', '{$route}', '{$httpMethod}', {$activeInt}, {$corsInt}, {$credentialsInt}, NOW(), NOW()
FROM apis a
WHERE a.name = '{$apiName}'
AND NOT EXISTS (SELECT 1 FROM api_endpoints e WHERE e.api_id = a.id AND e.name = '{$endpointName}');
SQL;
}
Comment on lines +48 to +68
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

http_method column type mismatch in insertEndpoint().

The schema defines http_method as an array type (per Version20190422160409.php), but this method inserts it as a plain string. While this method isn't used in the current migration, future usage could cause data type issues.

The seeder likely uses Doctrine's array serialization. If this helper is intended for use, consider matching the seeder's approach or documenting that this method requires the caller to pre-serialize the HTTP method array.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@database/migrations/config/APIEndpointsMigrationHelper.php` around lines 48 -
68, The insertEndpoint helper inserts http_method as a plain string even though
the schema (see Version20190422160409.php) defines http_method as an
array/serialized field; update insertEndpoint to accept the HTTP methods as an
array or a pre-serialized string and ensure it serializes arrays the same way
the seeder/Doctrine does (or validate caller-provided serialization), i.e.,
change the signature/use of insertEndpoint (and the local variable handling for
$httpMethod) so that when given an array it converts it to the correct
serialized format used by the db, or document that callers must pass a
pre-serialized $httpMethod string.


/**
* Generate DELETE for api_endpoints table.
*
* @param string $apiName API identifier
* @param string $endpointName Endpoint identifier to delete
* @return string SQL DELETE statement
*/
protected function deleteEndpoint(string $apiName, string $endpointName): string
{
return <<<SQL
DELETE e FROM api_endpoints e
INNER JOIN apis a ON a.id = e.api_id
WHERE a.name = '{$apiName}'
AND e.name = '{$endpointName}';
SQL;
}

/**
* Generate idempotent INSERT for api_scopes table.
*
* @param string $apiName API identifier (e.g., 'summits', 'resource-server')
* @param string $scopeName Full scope URI (e.g., 'https://example.com/summits/read')
* @param string $shortDesc Short description for the scope
* @param string $desc Full description for the scope
* @param bool $active Whether the scope is active (default: true)
* @param bool $default Whether the scope is default (default: false)
* @param bool $system Whether the scope is a system scope (default: false)
* @return string SQL INSERT statement
*/
protected function insertApiScope(
string $apiName,
string $scopeName,
string $shortDesc,
string $desc,
bool $active = true,
bool $default = false,
bool $system = false
): string {
$activeInt = $active ? 1 : 0;
$defaultInt = $default ? 1 : 0;
$systemInt = $system ? 1 : 0;

return <<<SQL
INSERT INTO api_scopes (api_id, name, short_description, description, active, `default`, `system`, created_at, updated_at)
SELECT a.id, '{$scopeName}', '{$shortDesc}', '{$desc}', {$activeInt}, {$defaultInt}, {$systemInt}, NOW(), NOW()
FROM apis a
WHERE a.name = '{$apiName}'
AND NOT EXISTS (SELECT 1 FROM api_scopes s WHERE s.api_id = a.id AND s.name = '{$scopeName}');
SQL;
}

/**
* Generate idempotent INSERT for endpoint_api_scopes table.
*
* Links an endpoint to a scope by their names.
*
* @param string $apiName API identifier (e.g., 'summits')
* @param string $endpointName Endpoint identifier (e.g., 'get-sponsor-extra-questions')
* @param string $scopeName Full scope URI
* @return string SQL INSERT statement
*/
protected function insertEndpointScope(string $apiName, string $endpointName, string $scopeName): string
{
return <<<SQL
INSERT INTO endpoint_api_scopes (api_endpoint_id, scope_id, created_at, updated_at)
SELECT e.id, s.id, NOW(), NOW()
FROM api_endpoints e
INNER JOIN apis a ON a.id = e.api_id
INNER JOIN api_scopes s ON s.api_id = a.id
WHERE a.name = '{$apiName}'
AND e.name = '{$endpointName}'
AND s.name = '{$scopeName}'
AND NOT EXISTS (
SELECT 1 FROM endpoint_api_scopes eas
WHERE eas.api_endpoint_id = e.id AND eas.scope_id = s.id
);
SQL;
}

/**
* Generate idempotent INSERT for endpoint_api_authz_groups table.
*
* Links an endpoint to an authorization group by slug.
*
* @param string $apiName API identifier (e.g., 'summits')
* @param string $endpointName Endpoint identifier
* @param string $groupSlug Group slug (e.g., 'sponsors-external-users')
* @return string SQL INSERT statement
*/
protected function insertEndpointAuthzGroup(string $apiName, string $endpointName, string $groupSlug): string
{
return <<<SQL
INSERT INTO endpoint_api_authz_groups (api_endpoint_id, group_slug, created_at, updated_at)
SELECT e.id, '{$groupSlug}', NOW(), NOW()
FROM api_endpoints e
INNER JOIN apis a ON a.id = e.api_id
WHERE a.name = '{$apiName}'
AND e.name = '{$endpointName}'
AND NOT EXISTS (
SELECT 1 FROM endpoint_api_authz_groups eag
WHERE eag.api_endpoint_id = e.id AND eag.group_slug = '{$groupSlug}'
);
SQL;
}

/**
* Generate DELETE for endpoint_api_authz_groups table.
*
* @param string $apiName API identifier
* @param string $endpointName Endpoint identifier
* @param string $groupSlug Group slug to remove
* @return string SQL DELETE statement
*/
protected function deleteEndpointAuthzGroup(string $apiName, string $endpointName, string $groupSlug): string
{
return <<<SQL
DELETE eag FROM endpoint_api_authz_groups eag
INNER JOIN api_endpoints e ON e.id = eag.api_endpoint_id
INNER JOIN apis a ON a.id = e.api_id
WHERE a.name = '{$apiName}'
AND e.name = '{$endpointName}'
AND eag.group_slug = '{$groupSlug}';
SQL;
}

/**
* Generate DELETE for endpoint_api_scopes table (all associations for given scopes).
*
* Constrained by API to prevent removing associations for other APIs that may
* reuse the same scope URI (api_scopes.name has no global uniqueness constraint).
*
* @param string $apiName API identifier (e.g., 'summits')
* @param array $scopes List of scope URIs to remove associations for
* @return string SQL DELETE statement
*/
protected function deleteScopesEndpoints(string $apiName, array $scopes): string
{
$scopeList = "'" . implode("', '", $scopes) . "'";
return <<<SQL
DELETE eas FROM endpoint_api_scopes eas
INNER JOIN api_scopes s ON s.id = eas.scope_id
INNER JOIN apis a ON a.id = s.api_id
WHERE a.name = '{$apiName}'
AND s.name IN ({$scopeList});
SQL;
}

/**
* Generate DELETE for api_scopes table.
*
* @param string $apiName API identifier
* @param array $scopes List of scope URIs to delete
* @return string SQL DELETE statement
*/
protected function deleteApiScopes(string $apiName, array $scopes): string
{
$scopeList = "'" . implode("', '", $scopes) . "'";
return <<<SQL
DELETE s FROM api_scopes s
INNER JOIN apis a ON a.id = s.api_id
WHERE a.name = '{$apiName}'
AND s.name IN ({$scopeList});
SQL;
}
}
Loading
Loading