feat: add marketplace metrics, privacy features, and service registry endpoints
- Add Prometheus metrics for marketplace API throughput and error rates with new dashboard panels - Implement confidential transaction models with encryption support and access control - Add key management system with registration, rotation, and audit logging - Create services and registry routers for service discovery and management - Integrate ZK proof generation for privacy-preserving receipts - Add metrics instru
This commit is contained in:
635
ecosystem-certification/registry/api-specification.yaml
Normal file
635
ecosystem-certification/registry/api-specification.yaml
Normal file
@ -0,0 +1,635 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: AITBC Ecosystem Registry API
|
||||
description: Public registry API for certified AITBC partners, SDKs, and integrations
|
||||
version: 1.0.0
|
||||
contact:
|
||||
name: AITBC Ecosystem Team
|
||||
email: ecosystem@aitbc.io
|
||||
license:
|
||||
name: MIT
|
||||
url: https://opensource.org/licenses/MIT
|
||||
|
||||
servers:
|
||||
- url: https://registry.aitbc.io/api/v1
|
||||
description: Production server
|
||||
- url: https://staging-registry.aitbc.io/api/v1
|
||||
description: Staging server
|
||||
|
||||
paths:
|
||||
/partners:
|
||||
get:
|
||||
summary: List certified partners
|
||||
description: Retrieve a paginated list of all certified partners
|
||||
tags:
|
||||
- Partners
|
||||
parameters:
|
||||
- name: level
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [bronze, silver, gold]
|
||||
description: Filter by certification level
|
||||
- name: language
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by SDK language
|
||||
- name: category
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [payment, erp, analytics, infrastructure]
|
||||
description: Filter by partner category
|
||||
- name: status
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [active, suspended, expired]
|
||||
description: Filter by certification status
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 1
|
||||
description: Page number
|
||||
- name: limit
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 20
|
||||
maximum: 100
|
||||
description: Items per page
|
||||
responses:
|
||||
'200':
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
partners:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PartnerSummary'
|
||||
pagination:
|
||||
$ref: '#/components/schemas/Pagination'
|
||||
filters:
|
||||
type: object
|
||||
description: Applied filters
|
||||
|
||||
/partners/{partnerId}:
|
||||
get:
|
||||
summary: Get partner details
|
||||
description: Retrieve detailed information about a certified partner
|
||||
tags:
|
||||
- Partners
|
||||
parameters:
|
||||
- name: partnerId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Unique partner identifier
|
||||
responses:
|
||||
'200':
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PartnerDetail'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
|
||||
/partners/{partnerId}/certification:
|
||||
get:
|
||||
summary: Get certification details
|
||||
description: Retrieve certification information for a partner
|
||||
tags:
|
||||
- Certification
|
||||
parameters:
|
||||
- name: partnerId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Certification'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
|
||||
/partners/{partnerId}/verify:
|
||||
get:
|
||||
summary: Verify certification
|
||||
description: Verify if a partner's certification is valid
|
||||
tags:
|
||||
- Certification
|
||||
parameters:
|
||||
- name: partnerId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Verification result
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
valid:
|
||||
type: boolean
|
||||
level:
|
||||
type: string
|
||||
enum: [bronze, silver, gold]
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
verification_id:
|
||||
type: string
|
||||
|
||||
/sdks:
|
||||
get:
|
||||
summary: List certified SDKs
|
||||
description: Retrieve a list of all certified SDKs
|
||||
tags:
|
||||
- SDKs
|
||||
parameters:
|
||||
- name: language
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [python, java, javascript, typescript, go, rust]
|
||||
description: Filter by programming language
|
||||
- name: version
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
description: Filter by SDK version
|
||||
- name: level
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [bronze, silver, gold]
|
||||
description: Filter by certification level
|
||||
responses:
|
||||
'200':
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
sdks:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SDKSummary'
|
||||
|
||||
/sdks/{sdkId}:
|
||||
get:
|
||||
summary: Get SDK details
|
||||
description: Retrieve detailed information about a certified SDK
|
||||
tags:
|
||||
- SDKs
|
||||
parameters:
|
||||
- name: sdkId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Successful response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SDKDetail'
|
||||
|
||||
/search:
|
||||
get:
|
||||
summary: Search registry
|
||||
description: Search for partners, SDKs, and integrations
|
||||
tags:
|
||||
- Search
|
||||
parameters:
|
||||
- name: q
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Search query
|
||||
- name: type
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [partner, sdk, integration, all]
|
||||
default: all
|
||||
description: Search target type
|
||||
- name: level
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [bronze, silver, gold]
|
||||
description: Filter by certification level
|
||||
responses:
|
||||
'200':
|
||||
description: Search results
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SearchResult'
|
||||
total:
|
||||
type: integer
|
||||
query:
|
||||
type: string
|
||||
|
||||
/stats:
|
||||
get:
|
||||
summary: Registry statistics
|
||||
description: Get overall registry statistics
|
||||
tags:
|
||||
- Statistics
|
||||
responses:
|
||||
'200':
|
||||
description: Statistics
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RegistryStats'
|
||||
|
||||
/badges/{partnerId}/{level}.svg:
|
||||
get:
|
||||
summary: Get certification badge
|
||||
description: Retrieve SVG badge for certified partner
|
||||
tags:
|
||||
- Badges
|
||||
parameters:
|
||||
- name: partnerId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: level
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum: [bronze, silver, gold]
|
||||
responses:
|
||||
'200':
|
||||
description: SVG badge
|
||||
content:
|
||||
image/svg+xml:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
components:
|
||||
schemas:
|
||||
PartnerSummary:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: Unique partner identifier
|
||||
name:
|
||||
type: string
|
||||
description: Partner company name
|
||||
logo_url:
|
||||
type: string
|
||||
description: URL to partner logo
|
||||
description:
|
||||
type: string
|
||||
description: Brief partner description
|
||||
website:
|
||||
type: string
|
||||
format: uri
|
||||
description: Partner website URL
|
||||
certification_level:
|
||||
type: string
|
||||
enum: [bronze, silver, gold]
|
||||
description: Current certification level
|
||||
category:
|
||||
type: string
|
||||
enum: [payment, erp, analytics, infrastructure]
|
||||
description: Partner category
|
||||
languages:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Supported programming languages
|
||||
certified_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Certification date
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Certification expiration date
|
||||
|
||||
PartnerDetail:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/PartnerSummary'
|
||||
- type: object
|
||||
properties:
|
||||
contact_email:
|
||||
type: string
|
||||
format: email
|
||||
description: Contact email
|
||||
support_url:
|
||||
type: string
|
||||
format: uri
|
||||
description: Support documentation URL
|
||||
documentation_url:
|
||||
type: string
|
||||
format: uri
|
||||
description: API documentation URL
|
||||
github_url:
|
||||
type: string
|
||||
format: uri
|
||||
description: GitHub repository URL
|
||||
integration_count:
|
||||
type: integer
|
||||
description: Number of certified integrations
|
||||
test_results:
|
||||
type: object
|
||||
properties:
|
||||
api_compliance:
|
||||
type: object
|
||||
properties:
|
||||
score:
|
||||
type: number
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
tests_run:
|
||||
type: integer
|
||||
tests_passed:
|
||||
type: integer
|
||||
security:
|
||||
type: object
|
||||
properties:
|
||||
score:
|
||||
type: number
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
vulnerabilities_found:
|
||||
type: integer
|
||||
critical_issues:
|
||||
type: integer
|
||||
performance:
|
||||
type: object
|
||||
properties:
|
||||
avg_response_time:
|
||||
type: number
|
||||
throughput:
|
||||
type: number
|
||||
uptime:
|
||||
type: number
|
||||
|
||||
Certification:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: Certification ID
|
||||
partner_id:
|
||||
type: string
|
||||
description: Partner ID
|
||||
level:
|
||||
type: string
|
||||
enum: [bronze, silver, gold]
|
||||
description: Certification level
|
||||
status:
|
||||
type: string
|
||||
enum: [active, suspended, expired]
|
||||
description: Certification status
|
||||
issued_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Issue date
|
||||
expires_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Expiration date
|
||||
test_results:
|
||||
type: object
|
||||
description: Test suite results
|
||||
security_report:
|
||||
type: object
|
||||
description: Security validation report
|
||||
criteria_met:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: List of certification criteria met
|
||||
|
||||
SDKSummary:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: SDK identifier
|
||||
name:
|
||||
type: string
|
||||
description: SDK name
|
||||
language:
|
||||
type: string
|
||||
description: Programming language
|
||||
version:
|
||||
type: string
|
||||
description: Latest version
|
||||
partner_id:
|
||||
type: string
|
||||
description: Partner ID
|
||||
partner_name:
|
||||
type: string
|
||||
description: Partner name
|
||||
certification_level:
|
||||
type: string
|
||||
enum: [bronze, silver, gold]
|
||||
download_url:
|
||||
type: string
|
||||
format: uri
|
||||
description: Download URL
|
||||
documentation_url:
|
||||
type: string
|
||||
format: uri
|
||||
description: Documentation URL
|
||||
certified_at:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
SDKDetail:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SDKSummary'
|
||||
- type: object
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
description: SDK description
|
||||
repository_url:
|
||||
type: string
|
||||
format: uri
|
||||
description: Source repository URL
|
||||
package_name:
|
||||
type: string
|
||||
description: Package name (pip, npm, maven)
|
||||
dependencies:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Key dependencies
|
||||
supported_versions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: Supported AITBC API versions
|
||||
installation_command:
|
||||
type: string
|
||||
description: Installation command
|
||||
quick_start:
|
||||
type: string
|
||||
description: Quick start code snippet
|
||||
|
||||
SearchResult:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum: [partner, sdk, integration]
|
||||
description: Result type
|
||||
id:
|
||||
type: string
|
||||
description: Item ID
|
||||
name:
|
||||
type: string
|
||||
description: Item name
|
||||
description:
|
||||
type: string
|
||||
description: Item description
|
||||
certification_level:
|
||||
type: string
|
||||
enum: [bronze, silver, gold]
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
description: Item URL
|
||||
relevance_score:
|
||||
type: number
|
||||
description: Search relevance score
|
||||
|
||||
Pagination:
|
||||
type: object
|
||||
properties:
|
||||
page:
|
||||
type: integer
|
||||
description: Current page
|
||||
limit:
|
||||
type: integer
|
||||
description: Items per page
|
||||
total:
|
||||
type: integer
|
||||
description: Total items
|
||||
pages:
|
||||
type: integer
|
||||
description: Total pages
|
||||
has_next:
|
||||
type: boolean
|
||||
description: Has next page
|
||||
has_prev:
|
||||
type: boolean
|
||||
description: Has previous page
|
||||
|
||||
RegistryStats:
|
||||
type: object
|
||||
properties:
|
||||
total_partners:
|
||||
type: integer
|
||||
description: Total certified partners
|
||||
total_sdks:
|
||||
type: integer
|
||||
description: Total certified SDKs
|
||||
certification_breakdown:
|
||||
type: object
|
||||
properties:
|
||||
bronze:
|
||||
type: integer
|
||||
silver:
|
||||
type: integer
|
||||
gold:
|
||||
type: integer
|
||||
language_breakdown:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: integer
|
||||
description: Number of SDKs per language
|
||||
category_breakdown:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: integer
|
||||
description: Number of partners per category
|
||||
last_updated:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Last update timestamp
|
||||
|
||||
responses:
|
||||
NotFound:
|
||||
description: Resource not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
|
||||
BadRequest:
|
||||
description: Bad request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
details:
|
||||
type: object
|
||||
|
||||
securitySchemes:
|
||||
ApiKeyAuth:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: X-API-Key
|
||||
description: API key for authenticated endpoints
|
||||
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
|
||||
tags:
|
||||
- name: Partners
|
||||
description: Partner management and lookup
|
||||
- name: SDKs
|
||||
description: SDK information and downloads
|
||||
- name: Certification
|
||||
description: Certification verification and details
|
||||
- name: Search
|
||||
description: Registry search functionality
|
||||
- name: Statistics
|
||||
description: Registry statistics and metrics
|
||||
- name: Badges
|
||||
description: Certification badges
|
||||
55
ecosystem-certification/test-suite/README.md
Normal file
55
ecosystem-certification/test-suite/README.md
Normal file
@ -0,0 +1,55 @@
|
||||
# AITBC SDK Conformance Test Suite
|
||||
|
||||
Language-agnostic test suite for validating AITBC SDK implementations against the official API specification.
|
||||
|
||||
## Architecture
|
||||
|
||||
The test suite uses black-box HTTP API testing to validate SDK compliance:
|
||||
- **Mock AITBC Server**: Validates requests against OpenAPI spec
|
||||
- **Test Runners**: Docker containers for each language
|
||||
- **Test Fixtures**: JSON/YAML test cases
|
||||
- **Reporting**: Detailed compliance reports
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Run Bronze certification tests
|
||||
docker-compose run python-sdk bronze
|
||||
|
||||
# Run Silver certification tests
|
||||
docker-compose run python-sdk silver
|
||||
|
||||
# Run all tests
|
||||
docker-compose run python-sdk all
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
||||
```
|
||||
test-suite/
|
||||
├── fixtures/ # Test cases (JSON/YAML)
|
||||
├── runners/ # Language-specific test runners
|
||||
├── mock-server/ # OpenAPI mock server
|
||||
├── reports/ # Test results
|
||||
└── docker-compose.yml
|
||||
```
|
||||
|
||||
## Certification Levels
|
||||
|
||||
### Bronze Tests
|
||||
- API compliance
|
||||
- Authentication
|
||||
- Error handling
|
||||
- Data model validation
|
||||
|
||||
### Silver Tests
|
||||
- Performance benchmarks
|
||||
- Rate limiting
|
||||
- Retry logic
|
||||
- Async support
|
||||
|
||||
### Gold Tests
|
||||
- Enterprise features
|
||||
- Scalability
|
||||
- Security compliance
|
||||
- SLA validation
|
||||
175
ecosystem-certification/test-suite/certify-stripe.py
Normal file
175
ecosystem-certification/test-suite/certify-stripe.py
Normal file
@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Certify the AITBC Stripe connector as a validation of the certification system
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add test suite to path
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from runners.python.test_runner import ConformanceTestRunner
|
||||
from security.security_validator import SecurityValidator
|
||||
|
||||
|
||||
async def certify_stripe_connector():
|
||||
"""Run full certification on Stripe connector"""
|
||||
|
||||
print("=" * 60)
|
||||
print("AITBC Stripe Connector Certification")
|
||||
print("=" * 60)
|
||||
|
||||
# Configuration
|
||||
base_url = "http://localhost:8011" # Mock server
|
||||
api_key = "test-api-key"
|
||||
sdk_path = Path(__file__).parent.parent.parent / "enterprise-connectors" / "python-sdk"
|
||||
|
||||
# 1. Run conformance tests
|
||||
print("\n1. Running SDK Conformance Tests...")
|
||||
runner = ConformanceTestRunner(base_url, api_key)
|
||||
|
||||
# Run Bronze tests
|
||||
bronze_suite = Path(__file__).parent / "fixtures" / "bronze" / "api-compliance.json"
|
||||
bronze_result = await runner.run_suite(str(bronze_suite), "bronze")
|
||||
|
||||
# Check if Bronze passed
|
||||
if bronze_result.compliance_score < 95:
|
||||
print(f"\n❌ Bronze certification FAILED: {bronze_result.compliance_score:.1f}%")
|
||||
return False
|
||||
|
||||
print(f"\n✅ Bronze certification PASSED: {bronze_result.compliance_score:.1f}%")
|
||||
|
||||
# 2. Run security validation
|
||||
print("\n2. Running Security Validation...")
|
||||
validator = SecurityValidator()
|
||||
security_report = validator.validate(str(sdk_path), "bronze")
|
||||
|
||||
print(f"\nSecurity Score: {security_report.score}/100")
|
||||
print(f"Issues Found: {len(security_report.issues)}")
|
||||
|
||||
if security_report.blocked:
|
||||
print("\n❌ Security validation BLOCKED certification")
|
||||
for issue in security_report.issues:
|
||||
if issue.severity in ["critical", "high"]:
|
||||
print(f" - {issue.description} ({issue.severity})")
|
||||
return False
|
||||
|
||||
print("\n✅ Security validation PASSED")
|
||||
|
||||
# 3. Generate certification report
|
||||
print("\n3. Generating Certification Report...")
|
||||
|
||||
certification = {
|
||||
"partner": {
|
||||
"name": "AITBC",
|
||||
"id": "aitbc-official",
|
||||
"website": "https://aitbc.io",
|
||||
"description": "Official AITBC Python SDK with Stripe connector"
|
||||
},
|
||||
"sdk": {
|
||||
"name": "aitbc-enterprise-python",
|
||||
"version": "1.0.0",
|
||||
"language": "python",
|
||||
"repository": "https://github.com/aitbc/enterprise-connectors"
|
||||
},
|
||||
"certification": {
|
||||
"level": "bronze",
|
||||
"issued_at": "2024-01-15T00:00:00Z",
|
||||
"expires_at": "2025-01-15T00:00:00Z",
|
||||
"id": "CERT-STRIPE-001"
|
||||
},
|
||||
"test_results": {
|
||||
"api_compliance": {
|
||||
"score": bronze_result.compliance_score,
|
||||
"tests_run": bronze_result.total_tests,
|
||||
"tests_passed": bronze_result.passed_tests
|
||||
},
|
||||
"security": {
|
||||
"score": security_report.score,
|
||||
"vulnerabilities_found": len(security_report.issues),
|
||||
"critical_issues": sum(1 for i in security_report.issues if i.severity == "critical")
|
||||
}
|
||||
},
|
||||
"criteria_met": [
|
||||
"Core API compatibility",
|
||||
"Authentication support",
|
||||
"Error handling standards",
|
||||
"Data model compliance",
|
||||
"Async support",
|
||||
"Basic security practices",
|
||||
"Documentation completeness"
|
||||
]
|
||||
}
|
||||
|
||||
# Save report
|
||||
report_path = Path(__file__).parent / "reports" / "stripe-certification.json"
|
||||
report_path.parent.mkdir(exist_ok=True)
|
||||
|
||||
with open(report_path, 'w') as f:
|
||||
json.dump(certification, f, indent=2)
|
||||
|
||||
print(f"\n✅ Certification report saved to: {report_path}")
|
||||
|
||||
# 4. Generate badge
|
||||
print("\n4. Generating Certification Badge...")
|
||||
|
||||
badge_svg = f'''<svg xmlns="http://www.w3.org/2000/svg" width="120" height="20">
|
||||
<linearGradient id="b" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
||||
<stop offset="1" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="a">
|
||||
<rect width="120" height="20" rx="3" fill="#fff"/>
|
||||
</clipPath>
|
||||
<g clip-path="url(#a)">
|
||||
<path fill="#555" d="M0 0h55v20H0z"/>
|
||||
<path fill="#CD7F32" d="M55 0h65v20H55z"/>
|
||||
<path fill="url(#b)" d="M0 0h120v20H0z"/>
|
||||
</g>
|
||||
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
||||
<text x="27.5" y="15" fill="#010101" fill-opacity=".3">AITBC</text>
|
||||
<text x="27.5" y="14">AITBC</text>
|
||||
<text x="87.5" y="15" fill="#010101" fill-opacity=".3">Bronze</text>
|
||||
<text x="87.5" y="14">Bronze</text>
|
||||
</g>
|
||||
</svg>'''
|
||||
|
||||
badge_path = Path(__file__).parent / "reports" / "stripe-bronze.svg"
|
||||
with open(badge_path, 'w') as f:
|
||||
f.write(badge_svg)
|
||||
|
||||
print(f"✅ Badge saved to: {badge_path}")
|
||||
|
||||
# 5. Summary
|
||||
print("\n" + "=" * 60)
|
||||
print("CERTIFICATION COMPLETE")
|
||||
print("=" * 60)
|
||||
print(f"Partner: AITBC")
|
||||
print(f"SDK: aitbc-enterprise-python (Stripe connector)")
|
||||
print(f"Level: Bronze")
|
||||
print(f"API Compliance: {bronze_result.compliance_score:.1f}%")
|
||||
print(f"Security Score: {security_report.score}/100")
|
||||
print(f"Certification ID: CERT-STRIPE-001")
|
||||
print(f"Valid Until: 2025-01-15")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point"""
|
||||
success = await certify_stripe_connector()
|
||||
|
||||
if success:
|
||||
print("\n🎉 Stripe connector successfully certified!")
|
||||
print("\nThe certification system is validated and ready for external partners.")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("\n❌ Certification failed. Please fix issues before proceeding.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@ -0,0 +1,264 @@
|
||||
{
|
||||
"name": "API Compliance Tests",
|
||||
"level": "bronze",
|
||||
"description": "Tests for core API compliance",
|
||||
"tests": [
|
||||
{
|
||||
"id": "BR-001",
|
||||
"name": "Health Check Endpoint",
|
||||
"description": "Validate health check endpoint returns proper response",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/health",
|
||||
"headers": {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"status": "healthy",
|
||||
"timestamp": "string",
|
||||
"version": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "BR-002",
|
||||
"name": "Authentication - Bearer Token",
|
||||
"description": "Validate bearer token authentication",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/v1/user/profile",
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid-token",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"id": "string",
|
||||
"email": "string",
|
||||
"created_at": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "BR-003",
|
||||
"name": "Authentication - Invalid Token",
|
||||
"description": "Validate proper error for invalid token",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/v1/user/profile",
|
||||
"headers": {
|
||||
"Authorization": "Bearer invalid-token",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 401,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"error": "AuthenticationError",
|
||||
"message": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "BR-004",
|
||||
"name": "Create Job - Valid Request",
|
||||
"description": "Validate job creation with valid parameters",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/api/v1/jobs",
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid-token",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"service_type": "gpu_compute",
|
||||
"spec": {
|
||||
"gpu_type": "A100",
|
||||
"count": 1,
|
||||
"duration": 3600
|
||||
},
|
||||
"metadata": {
|
||||
"name": "test-job"
|
||||
}
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 201,
|
||||
"headers": {
|
||||
"Content-Type": "application/json",
|
||||
"Location": "string"
|
||||
},
|
||||
"body": {
|
||||
"id": "string",
|
||||
"status": "pending",
|
||||
"created_at": "string",
|
||||
"estimated_completion": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "BR-005",
|
||||
"name": "Create Job - Invalid Parameters",
|
||||
"description": "Validate proper error for invalid job parameters",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/api/v1/jobs",
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid-token",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"service_type": "invalid_service",
|
||||
"spec": {}
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 400,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"error": "ValidationError",
|
||||
"message": "string",
|
||||
"details": {
|
||||
"field": "service_type",
|
||||
"issue": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "BR-006",
|
||||
"name": "Get Job - Valid ID",
|
||||
"description": "Validate job retrieval with valid ID",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/v1/jobs/job-123",
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid-token",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"id": "string",
|
||||
"status": "string",
|
||||
"created_at": "string",
|
||||
"updated_at": "string",
|
||||
"spec": "object",
|
||||
"result": "object|null"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "BR-007",
|
||||
"name": "Get Job - Not Found",
|
||||
"description": "Validate proper error for non-existent job",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/v1/jobs/nonexistent",
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid-token",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 404,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"error": "NotFoundError",
|
||||
"message": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "BR-008",
|
||||
"name": "List Jobs - With Pagination",
|
||||
"description": "Validate job listing with pagination",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/v1/jobs?limit=10&offset=0",
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid-token",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"jobs": "array",
|
||||
"total": "number",
|
||||
"limit": "number",
|
||||
"offset": "number",
|
||||
"has_more": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "BR-009",
|
||||
"name": "Error Response Format",
|
||||
"description": "Validate consistent error response format",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/api/v1/invalid-endpoint",
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid-token"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 404,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"error": "string",
|
||||
"message": "string",
|
||||
"request_id": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "BR-010",
|
||||
"name": "Rate Limit Headers",
|
||||
"description": "Validate rate limit headers are present",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/v1/jobs",
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid-token"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"X-RateLimit-Limit": "string",
|
||||
"X-RateLimit-Remaining": "string",
|
||||
"X-RateLimit-Reset": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
357
ecosystem-certification/test-suite/runners/python/test_runner.py
Normal file
357
ecosystem-certification/test-suite/runners/python/test_runner.py
Normal file
@ -0,0 +1,357 @@
|
||||
"""
|
||||
Python SDK conformance test runner for AITBC ecosystem certification
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any, Optional
|
||||
import aiohttp
|
||||
import pytest
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
# Import the SDK being tested
|
||||
try:
|
||||
from aitbc_enterprise import AITBCClient, ConnectorConfig
|
||||
except ImportError:
|
||||
print("ERROR: AITBC SDK not found. Please install it first.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class TestResult(BaseModel):
|
||||
"""Individual test result"""
|
||||
test_id: str
|
||||
name: str
|
||||
passed: bool
|
||||
duration: float
|
||||
error: Optional[str] = None
|
||||
details: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class SuiteResult(BaseModel):
|
||||
"""Test suite result"""
|
||||
suite_name: str
|
||||
level: str
|
||||
total_tests: int
|
||||
passed_tests: int
|
||||
failed_tests: int
|
||||
duration: float
|
||||
results: List[TestResult]
|
||||
compliance_score: float
|
||||
|
||||
|
||||
class ConformanceTestRunner:
|
||||
"""Main test runner for SDK conformance"""
|
||||
|
||||
def __init__(self, base_url: str, api_key: str):
|
||||
self.base_url = base_url
|
||||
self.api_key = api_key
|
||||
self.client: Optional[AITBCClient] = None
|
||||
self.results: List[TestResult] = []
|
||||
|
||||
async def run_suite(self, suite_path: str, level: str) -> SuiteResult:
|
||||
"""Run a test suite"""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Running {level.upper()} Certification Tests")
|
||||
print(f"{'='*60}")
|
||||
|
||||
# Load test suite
|
||||
with open(suite_path, 'r') as f:
|
||||
suite = json.load(f)
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# Initialize client
|
||||
config = ConnectorConfig(
|
||||
base_url=self.base_url,
|
||||
api_key=self.api_key,
|
||||
timeout=30.0
|
||||
)
|
||||
|
||||
async with AITBCClient(config) as client:
|
||||
self.client = client
|
||||
|
||||
# Run all tests
|
||||
for test in suite['tests']:
|
||||
result = await self._run_test(test)
|
||||
self.results.append(result)
|
||||
|
||||
# Print result
|
||||
status = "✓ PASS" if result.passed else "✗ FAIL"
|
||||
print(f"{status} {result.name} ({result.duration:.3f}s)")
|
||||
|
||||
if not result.passed:
|
||||
print(f" Error: {result.error}")
|
||||
|
||||
duration = time.time() - start_time
|
||||
|
||||
# Calculate results
|
||||
passed = sum(1 for r in self.results if r.passed)
|
||||
failed = len(self.results) - passed
|
||||
compliance_score = (passed / len(self.results)) * 100
|
||||
|
||||
suite_result = SuiteResult(
|
||||
suite_name=suite['name'],
|
||||
level=level,
|
||||
total_tests=len(self.results),
|
||||
passed_tests=passed,
|
||||
failed_tests=failed,
|
||||
duration=duration,
|
||||
results=self.results,
|
||||
compliance_score=compliance_score
|
||||
)
|
||||
|
||||
# Print summary
|
||||
self._print_summary(suite_result)
|
||||
|
||||
return suite_result
|
||||
|
||||
async def _run_test(self, test: Dict[str, Any]) -> TestResult:
|
||||
"""Run a single test"""
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
# Execute request based on test definition
|
||||
response_data = await self._execute_request(test['request'])
|
||||
|
||||
# Validate response
|
||||
validation_result = await self._validate_response(
|
||||
response_data,
|
||||
test.get('expected', {})
|
||||
)
|
||||
|
||||
if validation_result['passed']:
|
||||
return TestResult(
|
||||
test_id=test['id'],
|
||||
name=test['name'],
|
||||
passed=True,
|
||||
duration=time.time() - start_time,
|
||||
details=validation_result.get('details')
|
||||
)
|
||||
else:
|
||||
return TestResult(
|
||||
test_id=test['id'],
|
||||
name=test['name'],
|
||||
passed=False,
|
||||
duration=time.time() - start_time,
|
||||
error=validation_result['error'],
|
||||
details=validation_result.get('details')
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return TestResult(
|
||||
test_id=test['id'],
|
||||
name=test['name'],
|
||||
passed=False,
|
||||
duration=time.time() - start_time,
|
||||
error=str(e),
|
||||
details={"traceback": traceback.format_exc()}
|
||||
)
|
||||
|
||||
async def _execute_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Execute HTTP request using SDK"""
|
||||
method = request['method'].upper()
|
||||
path = request['path']
|
||||
headers = request.get('headers', {})
|
||||
body = request.get('body')
|
||||
|
||||
# Parse path parameters
|
||||
if '?' in path:
|
||||
path, query = path.split('?', 1)
|
||||
params = dict(q.split('=') for q in query.split('&'))
|
||||
else:
|
||||
params = {}
|
||||
|
||||
# Make request using SDK client
|
||||
if method == 'GET':
|
||||
response = await self.client.get(path, params=params)
|
||||
elif method == 'POST':
|
||||
response = await self.client.post(path, json=body)
|
||||
elif method == 'PUT':
|
||||
response = await self.client.put(path, json=body)
|
||||
elif method == 'DELETE':
|
||||
response = await self.client.delete(path)
|
||||
else:
|
||||
raise ValueError(f"Unsupported method: {method}")
|
||||
|
||||
return {
|
||||
'status': 200, # SDK handles status codes
|
||||
'headers': headers,
|
||||
'body': response
|
||||
}
|
||||
|
||||
async def _validate_response(
|
||||
self,
|
||||
response: Dict[str, Any],
|
||||
expected: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""Validate response against expectations"""
|
||||
errors = []
|
||||
details = {}
|
||||
|
||||
# Validate status code
|
||||
if 'status' in expected:
|
||||
if response['status'] != expected['status']:
|
||||
errors.append(
|
||||
f"Status mismatch: expected {expected['status']}, "
|
||||
f"got {response['status']}"
|
||||
)
|
||||
|
||||
# Validate headers
|
||||
if 'headers' in expected:
|
||||
for header, value in expected['headers'].items():
|
||||
if header not in response['headers']:
|
||||
errors.append(f"Missing header: {header}")
|
||||
elif value != 'string' and response['headers'][header] != value:
|
||||
errors.append(
|
||||
f"Header {header} mismatch: expected {value}, "
|
||||
f"got {response['headers'][header]}"
|
||||
)
|
||||
|
||||
# Validate body
|
||||
if 'body' in expected:
|
||||
body_errors = await self._validate_body(
|
||||
response['body'],
|
||||
expected['body']
|
||||
)
|
||||
errors.extend(body_errors)
|
||||
|
||||
return {
|
||||
'passed': len(errors) == 0,
|
||||
'error': '; '.join(errors) if errors else None,
|
||||
'details': details
|
||||
}
|
||||
|
||||
async def _validate_body(self, actual: Any, expected: Any) -> List[str]:
|
||||
"""Validate response body"""
|
||||
errors = []
|
||||
|
||||
if expected == 'string':
|
||||
if not isinstance(actual, str):
|
||||
errors.append(f"Expected string, got {type(actual).__name__}")
|
||||
elif expected == 'number':
|
||||
if not isinstance(actual, (int, float)):
|
||||
errors.append(f"Expected number, got {type(actual).__name__}")
|
||||
elif expected == 'boolean':
|
||||
if not isinstance(actual, bool):
|
||||
errors.append(f"Expected boolean, got {type(actual).__name__}")
|
||||
elif expected == 'array':
|
||||
if not isinstance(actual, list):
|
||||
errors.append(f"Expected array, got {type(actual).__name__}")
|
||||
elif expected == 'object':
|
||||
if not isinstance(actual, dict):
|
||||
errors.append(f"Expected object, got {type(actual).__name__}")
|
||||
elif expected == 'null':
|
||||
if actual is not None:
|
||||
errors.append(f"Expected null, got {actual}")
|
||||
elif isinstance(expected, dict):
|
||||
if not isinstance(actual, dict):
|
||||
errors.append(f"Expected object, got {type(actual).__name__}")
|
||||
else:
|
||||
for key, value in expected.items():
|
||||
if key not in actual:
|
||||
errors.append(f"Missing field: {key}")
|
||||
else:
|
||||
field_errors = await self._validate_body(actual[key], value)
|
||||
for error in field_errors:
|
||||
errors.append(f"{key}.{error}")
|
||||
|
||||
return errors
|
||||
|
||||
def _print_summary(self, result: SuiteResult):
|
||||
"""Print test suite summary"""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Test Suite Summary")
|
||||
print(f"{'='*60}")
|
||||
print(f"Suite: {result.suite_name}")
|
||||
print(f"Level: {result.level.upper()}")
|
||||
print(f"Total Tests: {result.total_tests}")
|
||||
print(f"Passed: {result.passed_tests}")
|
||||
print(f"Failed: {result.failed_tests}")
|
||||
print(f"Duration: {result.duration:.2f}s")
|
||||
print(f"Compliance Score: {result.compliance_score:.1f}%")
|
||||
|
||||
if result.failed_tests > 0:
|
||||
print(f"\nFailed Tests:")
|
||||
for test in result.results:
|
||||
if not test.passed:
|
||||
print(f" ✗ {test.name} - {test.error}")
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
|
||||
# Certification status
|
||||
if result.compliance_score >= 95:
|
||||
print(f"✓ CERTIFIED - {result.level.upper()}")
|
||||
else:
|
||||
print(f"✗ NOT CERTIFIED - Score below 95%")
|
||||
|
||||
def save_report(self, result: SuiteResult, output_dir: Path):
|
||||
"""Save test report to file"""
|
||||
report = {
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"suite": result.dict(),
|
||||
"sdk_version": "1.0.0", # Get from SDK
|
||||
"test_environment": {
|
||||
"base_url": self.base_url,
|
||||
"runner_version": "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
output_file = output_dir / f"report_{result.level}_{int(time.time())}.json"
|
||||
with open(output_file, 'w') as f:
|
||||
json.dump(report, f, indent=2)
|
||||
|
||||
print(f"\nReport saved to: {output_file}")
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="AITBC SDK Conformance Test Runner")
|
||||
parser.add_argument("--base-url", default="http://localhost:8011", help="AITBC API base URL")
|
||||
parser.add_argument("--api-key", required=True, help="API key for authentication")
|
||||
parser.add_argument("--level", choices=["bronze", "silver", "gold", "all"], default="bronze")
|
||||
parser.add_argument("--output-dir", default="./reports", help="Output directory for reports")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Create output directory
|
||||
output_dir = Path(args.output_dir)
|
||||
output_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Initialize test runner
|
||||
runner = ConformanceTestRunner(args.base_url, args.api_key)
|
||||
|
||||
# Run tests based on level
|
||||
if args.level == "all":
|
||||
levels = ["bronze", "silver", "gold"]
|
||||
else:
|
||||
levels = [args.level]
|
||||
|
||||
all_passed = True
|
||||
|
||||
for level in levels:
|
||||
suite_path = Path(__file__).parent.parent.parent / "fixtures" / level / "api-compliance.json"
|
||||
|
||||
if not suite_path.exists():
|
||||
print(f"ERROR: Test suite not found: {suite_path}")
|
||||
all_passed = False
|
||||
continue
|
||||
|
||||
result = await runner.run_suite(str(suite_path), level)
|
||||
runner.save_report(result, output_dir)
|
||||
|
||||
if result.compliance_score < 95:
|
||||
all_passed = False
|
||||
|
||||
# Exit with appropriate code
|
||||
sys.exit(0 if all_passed else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@ -0,0 +1,638 @@
|
||||
"""
|
||||
Security validation framework for AITBC SDK certification
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import tempfile
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any, Optional
|
||||
from dataclasses import dataclass, asdict
|
||||
from datetime import datetime
|
||||
import yaml
|
||||
|
||||
|
||||
@dataclass
|
||||
class SecurityIssue:
|
||||
"""Security issue representation"""
|
||||
tool: str
|
||||
severity: str # critical, high, medium, low
|
||||
type: str # vulnerability, dependency, code_issue
|
||||
description: str
|
||||
file_path: Optional[str] = None
|
||||
line_number: Optional[int] = None
|
||||
cve_id: Optional[str] = None
|
||||
remediation: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class SecurityReport:
|
||||
"""Security validation report"""
|
||||
sdk_path: str
|
||||
sdk_language: str
|
||||
timestamp: datetime
|
||||
issues: List[SecurityIssue]
|
||||
score: float
|
||||
certification_level: str
|
||||
blocked: bool
|
||||
|
||||
|
||||
class SecurityValidator:
|
||||
"""Main security validation orchestrator"""
|
||||
|
||||
def __init__(self):
|
||||
self.tools = {
|
||||
"python": PythonSecurityValidator(),
|
||||
"java": JavaSecurityValidator(),
|
||||
"javascript": JavaScriptSecurityValidator(),
|
||||
"typescript": TypeScriptSecurityValidator()
|
||||
}
|
||||
|
||||
def validate(self, sdk_path: str, certification_level: str = "bronze") -> SecurityReport:
|
||||
"""Validate SDK security"""
|
||||
sdk_path = Path(sdk_path).resolve()
|
||||
|
||||
# Detect language
|
||||
language = self._detect_language(sdk_path)
|
||||
if language not in self.tools:
|
||||
raise ValueError(f"Unsupported language: {language}")
|
||||
|
||||
# Run validation
|
||||
validator = self.tools[language]
|
||||
issues = validator.validate(sdk_path, certification_level)
|
||||
|
||||
# Calculate score and determine certification status
|
||||
score = self._calculate_score(issues, certification_level)
|
||||
blocked = self._should_block_certification(issues, certification_level)
|
||||
|
||||
return SecurityReport(
|
||||
sdk_path=str(sdk_path),
|
||||
sdk_language=language,
|
||||
timestamp=datetime.utcnow(),
|
||||
issues=issues,
|
||||
score=score,
|
||||
certification_level=certification_level,
|
||||
blocked=blocked
|
||||
)
|
||||
|
||||
def _detect_language(self, path: Path) -> str:
|
||||
"""Detect SDK programming language"""
|
||||
# Check for language-specific files
|
||||
if (path / "setup.py").exists() or (path / "pyproject.toml").exists():
|
||||
return "python"
|
||||
elif (path / "pom.xml").exists() or (path / "build.gradle").exists():
|
||||
return "java"
|
||||
elif (path / "package.json").exists():
|
||||
# Check if it's TypeScript
|
||||
if any(path.rglob("*.ts")):
|
||||
return "typescript"
|
||||
return "javascript"
|
||||
|
||||
raise ValueError("Could not detect SDK language")
|
||||
|
||||
def _calculate_score(self, issues: List[SecurityIssue], level: str) -> float:
|
||||
"""Calculate security score (0-100)"""
|
||||
weights = {
|
||||
"critical": 25,
|
||||
"high": 15,
|
||||
"medium": 5,
|
||||
"low": 1
|
||||
}
|
||||
|
||||
total_deduction = 0
|
||||
for issue in issues:
|
||||
total_deduction += weights.get(issue.severity, 0)
|
||||
|
||||
score = max(0, 100 - total_deduction)
|
||||
return score
|
||||
|
||||
def _should_block_certification(self, issues: List[SecurityIssue], level: str) -> bool:
|
||||
"""Determine if issues should block certification"""
|
||||
if level == "bronze":
|
||||
# Block for critical or high severity issues
|
||||
return any(i.severity in ["critical", "high"] for i in issues)
|
||||
elif level == "silver":
|
||||
# Block for critical issues
|
||||
return any(i.severity == "critical" for i in issues)
|
||||
elif level == "gold":
|
||||
# Block for any issues
|
||||
return len(issues) > 0
|
||||
|
||||
return False
|
||||
|
||||
def export_sarif(self, report: SecurityReport, output_path: str):
|
||||
"""Export report in SARIF format"""
|
||||
sarif = {
|
||||
"version": "2.1.0",
|
||||
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
|
||||
"runs": [
|
||||
{
|
||||
"tool": {
|
||||
"driver": {
|
||||
"name": "aitbc-security-validator",
|
||||
"version": "1.0.0",
|
||||
"informationUri": "https://aitbc.io/security"
|
||||
}
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"ruleId": f"{issue.tool}-{issue.type}",
|
||||
"level": self._map_severity_to_sarif(issue.severity),
|
||||
"message": {
|
||||
"text": issue.description
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": issue.file_path or ""
|
||||
},
|
||||
"region": {
|
||||
"startLine": issue.line_number or 1
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cve": issue.cve_id,
|
||||
"remediation": issue.remediation
|
||||
}
|
||||
}
|
||||
for issue in report.issues
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
with open(output_path, 'w') as f:
|
||||
json.dump(sarif, f, indent=2)
|
||||
|
||||
def _map_severity_to_sarif(self, severity: str) -> str:
|
||||
"""Map severity to SARIF level"""
|
||||
mapping = {
|
||||
"critical": "error",
|
||||
"high": "error",
|
||||
"medium": "warning",
|
||||
"low": "note"
|
||||
}
|
||||
return mapping.get(severity, "warning")
|
||||
|
||||
|
||||
class PythonSecurityValidator:
|
||||
"""Python-specific security validation"""
|
||||
|
||||
def validate(self, path: Path, level: str) -> List[SecurityIssue]:
|
||||
"""Run Python security checks"""
|
||||
issues = []
|
||||
|
||||
# Dependency scanning with safety
|
||||
issues.extend(self._scan_dependencies(path))
|
||||
|
||||
# Code analysis with bandit
|
||||
if level in ["silver", "gold"]:
|
||||
issues.extend(self._analyze_code(path))
|
||||
|
||||
# Check for secrets
|
||||
if level == "gold":
|
||||
issues.extend(self._scan_secrets(path))
|
||||
|
||||
return issues
|
||||
|
||||
def _scan_dependencies(self, path: Path) -> List[SecurityIssue]:
|
||||
"""Scan Python dependencies for vulnerabilities"""
|
||||
issues = []
|
||||
|
||||
# Find requirements files
|
||||
req_files = list(path.rglob("requirements*.txt")) + list(path.rglob("pyproject.toml"))
|
||||
|
||||
for req_file in req_files:
|
||||
try:
|
||||
# Run safety check
|
||||
result = subprocess.run(
|
||||
["safety", "check", "--json", "--file", str(req_file)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=path
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
data = json.loads(result.stdout)
|
||||
|
||||
for vuln in data:
|
||||
issues.append(SecurityIssue(
|
||||
tool="safety",
|
||||
severity=self._map_safety_severity(vuln.get("advisory", "")),
|
||||
type="dependency",
|
||||
description=vuln.get("advisory", ""),
|
||||
cve_id=vuln.get("cve"),
|
||||
remediation=f"Update {vuln.get('package')} to {vuln.get('analyzed_version')}"
|
||||
))
|
||||
except (subprocess.CalledProcessError, json.JSONDecodeError, FileNotFoundError):
|
||||
# Safety not installed or failed
|
||||
pass
|
||||
|
||||
return issues
|
||||
|
||||
def _analyze_code(self, path: Path) -> List[SecurityIssue]:
|
||||
"""Analyze Python code for security issues"""
|
||||
issues = []
|
||||
|
||||
try:
|
||||
# Run bandit
|
||||
result = subprocess.run(
|
||||
["bandit", "-r", str(path), "-f", "json"],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.stdout:
|
||||
data = json.loads(result.stdout)
|
||||
|
||||
for issue in data.get("results", []):
|
||||
issues.append(SecurityIssue(
|
||||
tool="bandit",
|
||||
severity=issue.get("issue_severity", "medium").lower(),
|
||||
type="code_issue",
|
||||
description=issue.get("issue_text", ""),
|
||||
file_path=issue.get("filename"),
|
||||
line_number=issue.get("line_number"),
|
||||
remediation=issue.get("issue_cwe", {}).get("link")
|
||||
))
|
||||
except (subprocess.CalledProcessError, json.JSONDecodeError, FileNotFoundError):
|
||||
# Bandit not installed or failed
|
||||
pass
|
||||
|
||||
return issues
|
||||
|
||||
def _scan_secrets(self, path: Path) -> List[SecurityIssue]:
|
||||
"""Scan for hardcoded secrets"""
|
||||
issues = []
|
||||
|
||||
try:
|
||||
# Run truffleHog
|
||||
result = subprocess.run(
|
||||
["trufflehog", "--json", str(path)],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.stdout:
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
if line:
|
||||
finding = json.loads(line)
|
||||
issues.append(SecurityIssue(
|
||||
tool="trufflehog",
|
||||
severity="high",
|
||||
type="code_issue",
|
||||
description="Hardcoded secret detected",
|
||||
file_path=finding.get("path"),
|
||||
line_number=finding.get("line"),
|
||||
remediation="Remove hardcoded secret and use environment variables"
|
||||
))
|
||||
except (subprocess.CalledProcessError, json.JSONDecodeError, FileNotFoundError):
|
||||
# TruffleHog not installed or failed
|
||||
pass
|
||||
|
||||
return issues
|
||||
|
||||
def _map_safety_severity(self, advisory: str) -> str:
|
||||
"""Map safety advisory to severity"""
|
||||
advisory_lower = advisory.lower()
|
||||
if any(word in advisory_lower for word in ["critical", "remote code execution"]):
|
||||
return "critical"
|
||||
elif any(word in advisory_lower for word in ["high", "execution", "bypass"]):
|
||||
return "high"
|
||||
elif any(word in advisory_lower for word in ["medium"]):
|
||||
return "medium"
|
||||
else:
|
||||
return "low"
|
||||
|
||||
|
||||
class JavaSecurityValidator:
|
||||
"""Java-specific security validation"""
|
||||
|
||||
def validate(self, path: Path, level: str) -> List[SecurityIssue]:
|
||||
"""Run Java security checks"""
|
||||
issues = []
|
||||
|
||||
# Dependency scanning with OWASP Dependency Check
|
||||
issues.extend(self._scan_dependencies(path))
|
||||
|
||||
# Code analysis with SpotBugs
|
||||
if level in ["silver", "gold"]:
|
||||
issues.extend(self._analyze_code(path))
|
||||
|
||||
return issues
|
||||
|
||||
def _scan_dependencies(self, path: Path) -> List[SecurityIssue]:
|
||||
"""Scan Java dependencies for vulnerabilities"""
|
||||
issues = []
|
||||
|
||||
# Look for pom.xml or build.gradle
|
||||
pom_file = path / "pom.xml"
|
||||
gradle_file = path / "build.gradle"
|
||||
|
||||
if pom_file.exists():
|
||||
# Run Maven dependency check
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["mvn", "org.owasp:dependency-check-maven:check"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=path
|
||||
)
|
||||
|
||||
# Parse XML report
|
||||
report_path = path / "target" / "dependency-check-report.xml"
|
||||
if report_path.exists():
|
||||
issues.extend(self._parse_dependency_check_report(report_path))
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
|
||||
elif gradle_file.exists():
|
||||
# Run Gradle dependency check
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["./gradlew", "dependencyCheckAnalyze"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=path
|
||||
)
|
||||
|
||||
# Parse XML report
|
||||
report_path = path / "build" / "reports" / "dependency-check-report.xml"
|
||||
if report_path.exists():
|
||||
issues.extend(self._parse_dependency_check_report(report_path))
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
|
||||
return issues
|
||||
|
||||
def _parse_dependency_check_report(self, report_path: Path) -> List[SecurityIssue]:
|
||||
"""Parse OWASP Dependency Check XML report"""
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
issues = []
|
||||
try:
|
||||
tree = ET.parse(report_path)
|
||||
root = tree.getroot()
|
||||
|
||||
for vulnerability in root.findall(".//vulnerability"):
|
||||
name = vulnerability.get("name")
|
||||
severity = vulnerability.get("severity")
|
||||
cve = vulnerability.get("cve")
|
||||
|
||||
# Map severity
|
||||
if severity.upper() in ["CRITICAL", "HIGH"]:
|
||||
mapped_severity = "high"
|
||||
elif severity.upper() == "MEDIUM":
|
||||
mapped_severity = "medium"
|
||||
else:
|
||||
mapped_severity = "low"
|
||||
|
||||
issues.append(SecurityIssue(
|
||||
tool="dependency-check",
|
||||
severity=mapped_severity,
|
||||
type="dependency",
|
||||
description=f"Vulnerability in {name}",
|
||||
cve_id=cve,
|
||||
remediation="Update dependency to patched version"
|
||||
))
|
||||
except ET.ParseError:
|
||||
pass
|
||||
|
||||
return issues
|
||||
|
||||
def _analyze_code(self, path: Path) -> List[SecurityIssue]:
|
||||
"""Analyze Java code with SpotBugs"""
|
||||
issues = []
|
||||
|
||||
try:
|
||||
# Run SpotBugs
|
||||
result = subprocess.run(
|
||||
["spotbugs", "-textui", "-xml:withMessages", "-low", str(path)],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
# Parse SpotBugs XML report
|
||||
report_path = path / "spotbugsXml.xml"
|
||||
if report_path.exists():
|
||||
issues.extend(self._parse_spotbugs_report(report_path))
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
|
||||
return issues
|
||||
|
||||
def _parse_spotbugs_report(self, report_path: Path) -> List[SecurityIssue]:
|
||||
"""Parse SpotBugs XML report"""
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
issues = []
|
||||
try:
|
||||
tree = ET.parse(report_path)
|
||||
root = tree.getroot()
|
||||
|
||||
for instance in root.findall(".//BugInstance"):
|
||||
bug_type = instance.get("type")
|
||||
priority = instance.get("priority")
|
||||
|
||||
# Map priority to severity
|
||||
if priority == "1":
|
||||
severity = "high"
|
||||
elif priority == "2":
|
||||
severity = "medium"
|
||||
else:
|
||||
severity = "low"
|
||||
|
||||
source_line = instance.find(".//SourceLine")
|
||||
if source_line is not None:
|
||||
issues.append(SecurityIssue(
|
||||
tool="spotbugs",
|
||||
severity=severity,
|
||||
type="code_issue",
|
||||
description=bug_type,
|
||||
file_path=source_line.get("sourcepath"),
|
||||
line_number=int(source_line.get("start", 0)),
|
||||
remediation=f"Fix {bug_type} security issue"
|
||||
))
|
||||
except ET.ParseError:
|
||||
pass
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
class JavaScriptSecurityValidator:
|
||||
"""JavaScript-specific security validation"""
|
||||
|
||||
def validate(self, path: Path, level: str) -> List[SecurityIssue]:
|
||||
"""Run JavaScript security checks"""
|
||||
issues = []
|
||||
|
||||
# Dependency scanning with npm audit
|
||||
issues.extend(self._scan_dependencies(path))
|
||||
|
||||
# Code analysis with ESLint security rules
|
||||
if level in ["silver", "gold"]:
|
||||
issues.extend(self._analyze_code(path))
|
||||
|
||||
return issues
|
||||
|
||||
def _scan_dependencies(self, path: Path) -> List[SecurityIssue]:
|
||||
"""Scan npm dependencies for vulnerabilities"""
|
||||
issues = []
|
||||
|
||||
package_json = path / "package.json"
|
||||
if not package_json.exists():
|
||||
return issues
|
||||
|
||||
try:
|
||||
# Run npm audit
|
||||
result = subprocess.run(
|
||||
["npm", "audit", "--json"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=path
|
||||
)
|
||||
|
||||
if result.stdout:
|
||||
data = json.loads(result.stdout)
|
||||
|
||||
for advisory_id, advisory in data.get("vulnerabilities", {}).items():
|
||||
severity = advisory.get("severity", "low")
|
||||
|
||||
issues.append(SecurityIssue(
|
||||
tool="npm-audit",
|
||||
severity=severity,
|
||||
type="dependency",
|
||||
description=advisory.get("title", ""),
|
||||
cve_id=advisory.get("cwe"),
|
||||
remediation=f"Run npm audit fix"
|
||||
))
|
||||
except (subprocess.CalledProcessError, json.JSONDecodeError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
return issues
|
||||
|
||||
def _analyze_code(self, path: Path) -> List[SecurityIssue]:
|
||||
"""Analyze JavaScript code with ESLint"""
|
||||
issues = []
|
||||
|
||||
try:
|
||||
# Run ESLint with security plugin
|
||||
result = subprocess.run(
|
||||
["npx", "eslint", "--format", "json", str(path)],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.stdout:
|
||||
data = json.loads(result.stdout)
|
||||
|
||||
for file_result in data:
|
||||
for message in file_result.get("messages", []):
|
||||
if "security" in message.get("ruleId", "").lower():
|
||||
issues.append(SecurityIssue(
|
||||
tool="eslint",
|
||||
severity="medium",
|
||||
type="code_issue",
|
||||
description=message.get("message"),
|
||||
file_path=file_result.get("filePath"),
|
||||
line_number=message.get("line"),
|
||||
remediation=f"Fix {message.get('ruleId')} issue"
|
||||
))
|
||||
except (subprocess.CalledProcessError, json.JSONDecodeError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
class TypeScriptSecurityValidator(JavaScriptSecurityValidator):
|
||||
"""TypeScript-specific security validation (inherits from JavaScript)"""
|
||||
|
||||
def validate(self, path: Path, level: str) -> List[SecurityIssue]:
|
||||
"""Run TypeScript security checks"""
|
||||
# Run JavaScript checks first
|
||||
issues = super().validate(path, level)
|
||||
|
||||
# Additional TypeScript-specific checks
|
||||
if level == "gold":
|
||||
issues.extend(self._check_typescript_config(path))
|
||||
|
||||
return issues
|
||||
|
||||
def _check_typescript_config(self, path: Path) -> List[SecurityIssue]:
|
||||
"""Check TypeScript configuration for security"""
|
||||
issues = []
|
||||
|
||||
tsconfig = path / "tsconfig.json"
|
||||
if tsconfig.exists():
|
||||
try:
|
||||
with open(tsconfig) as f:
|
||||
config = json.load(f)
|
||||
|
||||
compiler_options = config.get("compilerOptions", {})
|
||||
|
||||
# Check for implicit any
|
||||
if compiler_options.get("noImplicitAny") is not True:
|
||||
issues.append(SecurityIssue(
|
||||
tool="typescript-config",
|
||||
severity="low",
|
||||
type="code_issue",
|
||||
description="TypeScript should disable implicit any",
|
||||
file_path=str(tsconfig),
|
||||
remediation="Set noImplicitAny to true"
|
||||
))
|
||||
|
||||
# Check for strict mode
|
||||
if compiler_options.get("strict") is not True:
|
||||
issues.append(SecurityIssue(
|
||||
tool="typescript-config",
|
||||
severity="low",
|
||||
type="code_issue",
|
||||
description="TypeScript should use strict mode",
|
||||
file_path=str(tsconfig),
|
||||
remediation="Set strict to true"
|
||||
))
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def main():
|
||||
"""CLI entry point"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="AITBC SDK Security Validator")
|
||||
parser.add_argument("sdk_path", help="Path to SDK directory")
|
||||
parser.add_argument("--level", choices=["bronze", "silver", "gold"], default="bronze")
|
||||
parser.add_argument("--output", help="Output SARIF report path")
|
||||
parser.add_argument("--format", choices=["json", "sarif"], default="json")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Run validation
|
||||
validator = SecurityValidator()
|
||||
report = validator.validate(args.sdk_path, args.level)
|
||||
|
||||
# Output results
|
||||
if args.format == "sarif" and args.output:
|
||||
validator.export_sarif(report, args.output)
|
||||
else:
|
||||
print(json.dumps(asdict(report), indent=2, default=str))
|
||||
|
||||
# Exit with error if blocked
|
||||
if report.blocked:
|
||||
print(f"\nCERTIFICATION BLOCKED: Security issues found")
|
||||
for issue in report.issues:
|
||||
if issue.severity in ["critical", "high"]:
|
||||
print(f" - {issue.description} ({issue.severity})")
|
||||
exit(1)
|
||||
else:
|
||||
print(f"\nSECURITY CHECK PASSED: Score {report.score}/100")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user