From f25e90f292ba660e266fa398fc3688c5db4b4cdd Mon Sep 17 00:00:00 2001 From: tracer Date: Tue, 30 Apr 2024 11:13:24 +0200 Subject: [PATCH] added self for nameservers for openApi defaults. --- bindapi.json | 148 +++++ composer.json | 2 +- db/migrations/schema.php | 24 + public/openapi/bindapi.json | 684 +++++------------------- public/openapi/bindapi.json.bak | 537 +++++++++++++++++++ public/openapi/bootstrap.php | 6 + public/openapi/bootstrap.php2 | 5 + public/openapi/index.html | 2 +- src/Controller/CLIController.php | 137 ++++- src/Controller/RequestController.php | 36 +- src/Entity/Nameserver.php | 167 +++--- src/Repository/NameserverRepository.php | 31 +- src/Util/Console.php | 3 +- 13 files changed, 1114 insertions(+), 668 deletions(-) create mode 100644 bindapi.json create mode 100644 public/openapi/bindapi.json.bak create mode 100644 public/openapi/bootstrap.php2 diff --git a/bindapi.json b/bindapi.json new file mode 100644 index 0000000..9e0906e --- /dev/null +++ b/bindapi.json @@ -0,0 +1,148 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "bindAPI", + "version": "1.0.9" + }, + "servers": [ + { + "url": "{schema}://{hostname}/api", + "description": "The bindAPI URL.", + "variables": { + "schema": { + "enum": [ + "http", + "https" + ], + "default": "https" + }, + "hostname": { + "enum": [ + "ns1.24unix.net", + "ns2.24unix.net" + ], + "default": "ns2.24unix.net" + } + } + } + ], + "paths": { + "/ping": { + "get": { + "tags": [ + "Server" + ], + "description": "Checks for connectivity and valid APIkey", + "operationId": "ping", + "responses": { + "200": { + "description": "OK" + }, + "401": { + "description": "API key is missing or invalid." + } + }, + "security": [ + { + "Authorization": [] + } + ] + } + }, + "/version": { + "get": { + "tags": [ + "Server" + ], + "description": "Check the API version of the nameserver.", + "operationId": "version", + "responses": { + "200": { + "description": "x.y.z, aka major, minor, patch" + }, + "401": { + "description": "API key is missing or invalid." + } + }, + "security": [ + { + "Authorization": [] + } + ] + } + }, + "/domains": { + "get": { + "tags": [ + "Domains" + ], + "summary": "List all domains.", + "description": "Returns a list of all domains on this server.", + "operationId": "getAllDomains", + "responses": { + "200": { + "description": "OK" + }, + "401": { + "description": "API key is missing or invalid." + }, + "404": { + "description": "Domain not found." + } + }, + "security": [ + { + "Authorization": [] + } + ] + } + }, + "/domains/{name}": { + "get": { + "tags": [ + "Domains" + ], + "summary": "Returns a single domain.", + "description": "Returns information of a single domain specified by its domain name.", + "operationId": "getSingleDomain", + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "401": { + "description": "API key is missing or invalid." + }, + "404": { + "description": "Domain not found." + } + }, + "security": [] + } + } + }, + "components": { + "securitySchemes": { + "Authorization": { + "type": "apiKey", + "description": "Api Authentication", + "name": "X-API-Key", + "in": "header" + } + } + }, + "tags": [ + { + "name": "Server" + } + ] +} \ No newline at end of file diff --git a/composer.json b/composer.json index 55d5b57..8420f60 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "24unix/bindapi", "description": "manage Bind9 DNS server via REST API", "version": "1.0.9", - "build_number": "359", + "build_number": "361", "authors": [ { "name": "Micha Espey", diff --git a/db/migrations/schema.php b/db/migrations/schema.php index a3f403a..6de0a73 100644 --- a/db/migrations/schema.php +++ b/db/migrations/schema.php @@ -1126,6 +1126,30 @@ return array ( 'IS_GENERATED' => 'NEVER', 'GENERATION_EXPRESSION' => NULL, ), + 'self' => + array ( + 'TABLE_CATALOG' => 'def', + 'TABLE_NAME' => 'nameservers', + 'COLUMN_NAME' => 'self', + 'ORDINAL_POSITION' => 7, + 'COLUMN_DEFAULT' => NULL, + 'IS_NULLABLE' => 'NO', + 'DATA_TYPE' => 'enum', + 'CHARACTER_MAXIMUM_LENGTH' => 3, + 'CHARACTER_OCTET_LENGTH' => 12, + 'NUMERIC_PRECISION' => NULL, + 'NUMERIC_SCALE' => NULL, + 'DATETIME_PRECISION' => NULL, + 'CHARACTER_SET_NAME' => 'utf8mb4', + 'COLLATION_NAME' => 'utf8mb4_unicode_ci', + 'COLUMN_TYPE' => 'enum(\'yes\',\'no\')', + 'COLUMN_KEY' => '', + 'EXTRA' => '', + 'PRIVILEGES' => 'select,insert,update,references', + 'COLUMN_COMMENT' => '', + 'IS_GENERATED' => 'NEVER', + 'GENERATION_EXPRESSION' => NULL, + ), ), 'indexes' => array ( diff --git a/public/openapi/bindapi.json b/public/openapi/bindapi.json index c4d7fbc..0bef6a4 100644 --- a/public/openapi/bindapi.json +++ b/public/openapi/bindapi.json @@ -1,537 +1,149 @@ { - "openapi": "3.0.2", - "info": { - "title": "bindAPI", - "version": "0.0.2", - "description": "TODO …", - "contact": { - "name": "Micha Espey", - "email": "tracer@24unix.net" - } - }, - "servers": [ - { - "url": "{schema}://{hostname}/api", - "description": "The bindAPI URL.", - "variables": { - "schema": { - "enum": [ - "https", - "http" - ], - "default": "https" - }, - "hostname": { - "default": "ns1.24unix.net" - } - } - } - ], - "tags": [ - { - "name": "Server" - }, - { - "name": "DNS" - }, - { - "name": "Domains" - } - ], - "security": [ - { - "ApiKeyAuth": [] - } - ], - "paths": { - "/ping": { - "get": { - "tags": [ - "Server" - ], - "summary": "Returning pong.", - "description": "Can be used to check API or server availability.", - "operationId": "getPong", - "responses": { - "200": { - "$ref": "#/components/responses/ping" - }, - "401": { - "$ref": "#/components/responses/401-unauthorized" - } - }, - "security": [ - { - "Authorization": [ - "read" - ] - } - ] - } - }, - "/dyndns/{hostname}": { - "post": { - "tags": [ - "DNS" - ], - "summary": "Updated a DynDNS host.", - "description": "Updates a predefined custom DNS entry.", - "operationId": "updateDynDNS", - "parameters": [ - { - "name": "hostname", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "$ref": "#/components/requestBodies/dyndns-put" - }, - "responses": { - "200": { - "description": "OK" - }, - "204": { - "description": "No Content" - }, - "401": { - "description": "API key is missing or invalid." - }, - "404": { - "description": "Domain not found." - } - }, - "security": [ - { - "Authorization": [] - } - ] - } - }, - "/domains": { - "get": { - "tags": [ - "Domains" - ], - "summary": "Returns all domains.", - "description": "Returns information of a single domain specified by its domain name.", - "operationId": "getDomains", - "responses": { - "200": { - "$ref": "#/components/responses/domain-array" - }, - "401": { - "$ref": "#/components/responses/401-unauthorized" - } - }, - "security": [ - { - "Authorization": [ - "read" - ] - } - ] - - }, - "post": { - "tags": [ - "Domains" - ], - "summary": "Create a domain.", - "description": "Create a new domain.", - "operationId": "putDomains", - "requestBody": { - "$ref": "#/components/requestBodies/domain-post" - }, - "responses": { - "201": { - "$ref": "#/components/responses/201-created" - }, - "400": { - "$ref": "#/components/responses/400-bad-request" - }, - "401": { - "$ref": "#/components/responses/401-unauthorized" - } - } - } - }, - "/domains/{id}": { - "get": { - "tags": [ - "Domains" - ], - "summary": "Returns a single domain.", - "description": "Returns information of a single domain specified by its ID.", - "operationId": "getSingleDomain", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/200-ok" - }, - "400": { - "$ref": "#/components/responses/400-bad-request" - }, - "401": { - "$ref": "#/components/responses/401-unauthorized" - } - } - }, - "put": { - "tags": [ - "Domains" - ], - "summary": "Updates a domain.", - "description": "Updates a domain. Only supplied fields will be updated, existing won't be affected.", - "operationId": "putDomain", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "$ref": "#/components/requestBodies/domain-put" - }, - - "responses": { - "200": { - "description": "OK" - }, - "401": { - "description": "API key is missing or invalid." - }, - "404": { - "description": "Domain not found." - } - }, - "security": [ - { - "Authorization": [] - } - ] - }, - "delete": { - "tags": [ - "Domains" - ], - "summary": "Deletes a domain.", - "description": "Deletes a domain.", - "operationId": "73c6c14e6d84f759d2e09029cb7ab2be", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "$ref": "#/components/responses/204-no-content" - }, - "401": { - "$ref": "#/components/responses/401-unauthorized" - }, - "404": { - "$ref": "#/components/responses/404-not-found" - } - }, "security": [ - { - "Authorization": [] - } - ] - } - }, - "/domains/name/{name}": { - "get": { - "tags": [ - "Domains" - ], - "summary": "Returns a single domain by name.", - "description": "Returns information of a single domain specified by its domain name.", - "operationId": "getSingleDomainByName", - "parameters": [ - { - "name": "name", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "$ref": "#/components/responses/200-ok" - }, - "400": { - "$ref": "#/components/responses/400-bad-request" - }, - "401": { - "$ref": "#/components/responses/401-unauthorized" - } - } - } - } - }, - "components": { - "securitySchemes": { - "Authorization": { - "type": "apiKey", - "description": "Authentication Token", - "name": "X-API-Key", - "in": "header" - } - }, - "requestBodies": { - "dyndns-put": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/dyndns" - }, - "example": { - "a": "1.2.3.4", - "aaaa": "1bad::babe" - } - } - } - }, - "domain-post": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/domain" - } - } - } - }, - "domain-put": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/domain" - }, - "example": { - "name": "example.com", - "panel_id": "8" - } - } - } - } - }, - "responses": { - "ping": { - "description": "OK.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ping" - } - } - } - }, - "domain": { - "description": "OK.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/domain" - } - } - } - }, - "domain-array": { - "description": "OK.", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/domain" - } - } - } - } - }, - "200-ok": { - "description": "OK." - }, - "200-ok-updated": { - "description": "OK.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/created" - }, - "example": { - "id": 8 - } - } - } - }, - "201-created": { - "description": "Created.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/created" - }, - "example": { - "id": 8 - } - } - } - }, - "204-no-content": { - "description": "No content." - }, - "400-bad-request": { - "description": "Bad request.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - }, - "example": { - "code": "400 Bad Request", - "message": "Invalid request body." - } - } - } - }, - "401-unauthorized": { - "description": "Unauthorized.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - }, - "example": { - "code": "401 Unauthorized", - "message": "API key is missing or invalid." - } - } - } - }, - "404-not-found": { - "description": "The specified resource was not found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - }, - "example": { - "code": "404 Not Found", - "message": "The specified resource was not found." - } - } - } - } - }, - "schemas": { - "ping": { - "type": "object", - "properties": { - "response": { - "type": "string", - "example": "pong" - } - } - }, - "dyndns": { - "type": "object", - "properties": { - "a": { - "type": "string", - "example": "1.2.3.4" - }, - "aaaa": { - "type": "string", - "example": "1bad::babe" - } - } - - }, - "domain": { - "description": "Representation of a domain.\n", - "type": "object", - "properties": { - "id": { - "type": "integer", - "readOnly": true, - "description": "The ID of the domain.", - "example": 8 - }, - "name": { - "type": "string", - "description": "The ASCII representation of the domain.", - "example": "example.com" - }, - "panel_id": { - "type": "integer", - "description": "The KeyHelp Panel ID. Either this or at least one IP address is required", - "example": 4 - }, - "a": { - "type": "string", - "readOnly": true, - "description": "The IPv4 address.", - "example": "12.13.14.15" - }, - "aaaa": { - "type": "string", - "readOnly": true, - "description": "The IPv6 address.", - "example": "1bad::babe" - } - } - }, - "created": { - "type": "object", - "properties": { - "id": { - "type": "integer" - } - } - }, - "error": { - "type": "object", - "properties": { - "code": { - "type": "string" - }, - "message": { - "type": "string" - } - }, - "required": [ - "code", - "message#" - ] - } - } - } -} + "openapi": "3.0.0", + "info": { + "title": "bindAPI", + "version": "1.0.9" + }, + "servers": [ + { + "url": "{schema}://{hostname}/api", + "description": "The bindAPI URL.", + "variables": { + "schema": { + "enum": [ + "http", + "https" + ], + "default": "https" + }, + "hostname": { + "enum": [ + "ns1.24unix.net", + "ns2.24unix.net", + "ns3.24unix.net" + ], + "default": "ns2.24unix.net" + } + } + } + ], + "paths": { + "/ping": { + "get": { + "tags": [ + "Server" + ], + "description": "Checks for connectivity and valid APIkey", + "operationId": "ping", + "responses": { + "200": { + "description": "OK" + }, + "401": { + "description": "API key is missing or invalid." + } + }, + "security": [ + { + "Authorization": [] + } + ] + } + }, + "/version": { + "get": { + "tags": [ + "Server" + ], + "description": "Check the API version of the nameserver.", + "operationId": "version", + "responses": { + "200": { + "description": "x.y.z, aka major, minor, patch" + }, + "401": { + "description": "API key is missing or invalid." + } + }, + "security": [ + { + "Authorization": [] + } + ] + } + }, + "/domains": { + "get": { + "tags": [ + "Domains" + ], + "summary": "List all domains.", + "description": "Returns a list of all domains on this server.", + "operationId": "getAllDomains", + "responses": { + "200": { + "description": "OK" + }, + "401": { + "description": "API key is missing or invalid." + }, + "404": { + "description": "Domain not found." + } + }, + "security": [ + { + "Authorization": [] + } + ] + } + }, + "/domains/{name}": { + "get": { + "tags": [ + "Domains" + ], + "summary": "Returns a single domain.", + "description": "Returns information of a single domain specified by its domain name.", + "operationId": "getSingleDomain", + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "401": { + "description": "API key is missing or invalid." + }, + "404": { + "description": "Domain not found." + } + }, + "security": [] + } + } + }, + "components": { + "securitySchemes": { + "Authorization": { + "type": "apiKey", + "description": "Api Authentication", + "name": "X-API-Key", + "in": "header" + } + } + }, + "tags": [ + { + "name": "Server" + } + ] +} \ No newline at end of file diff --git a/public/openapi/bindapi.json.bak b/public/openapi/bindapi.json.bak new file mode 100644 index 0000000..c4d7fbc --- /dev/null +++ b/public/openapi/bindapi.json.bak @@ -0,0 +1,537 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "bindAPI", + "version": "0.0.2", + "description": "TODO …", + "contact": { + "name": "Micha Espey", + "email": "tracer@24unix.net" + } + }, + "servers": [ + { + "url": "{schema}://{hostname}/api", + "description": "The bindAPI URL.", + "variables": { + "schema": { + "enum": [ + "https", + "http" + ], + "default": "https" + }, + "hostname": { + "default": "ns1.24unix.net" + } + } + } + ], + "tags": [ + { + "name": "Server" + }, + { + "name": "DNS" + }, + { + "name": "Domains" + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "paths": { + "/ping": { + "get": { + "tags": [ + "Server" + ], + "summary": "Returning pong.", + "description": "Can be used to check API or server availability.", + "operationId": "getPong", + "responses": { + "200": { + "$ref": "#/components/responses/ping" + }, + "401": { + "$ref": "#/components/responses/401-unauthorized" + } + }, + "security": [ + { + "Authorization": [ + "read" + ] + } + ] + } + }, + "/dyndns/{hostname}": { + "post": { + "tags": [ + "DNS" + ], + "summary": "Updated a DynDNS host.", + "description": "Updates a predefined custom DNS entry.", + "operationId": "updateDynDNS", + "parameters": [ + { + "name": "hostname", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/dyndns-put" + }, + "responses": { + "200": { + "description": "OK" + }, + "204": { + "description": "No Content" + }, + "401": { + "description": "API key is missing or invalid." + }, + "404": { + "description": "Domain not found." + } + }, + "security": [ + { + "Authorization": [] + } + ] + } + }, + "/domains": { + "get": { + "tags": [ + "Domains" + ], + "summary": "Returns all domains.", + "description": "Returns information of a single domain specified by its domain name.", + "operationId": "getDomains", + "responses": { + "200": { + "$ref": "#/components/responses/domain-array" + }, + "401": { + "$ref": "#/components/responses/401-unauthorized" + } + }, + "security": [ + { + "Authorization": [ + "read" + ] + } + ] + + }, + "post": { + "tags": [ + "Domains" + ], + "summary": "Create a domain.", + "description": "Create a new domain.", + "operationId": "putDomains", + "requestBody": { + "$ref": "#/components/requestBodies/domain-post" + }, + "responses": { + "201": { + "$ref": "#/components/responses/201-created" + }, + "400": { + "$ref": "#/components/responses/400-bad-request" + }, + "401": { + "$ref": "#/components/responses/401-unauthorized" + } + } + } + }, + "/domains/{id}": { + "get": { + "tags": [ + "Domains" + ], + "summary": "Returns a single domain.", + "description": "Returns information of a single domain specified by its ID.", + "operationId": "getSingleDomain", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/200-ok" + }, + "400": { + "$ref": "#/components/responses/400-bad-request" + }, + "401": { + "$ref": "#/components/responses/401-unauthorized" + } + } + }, + "put": { + "tags": [ + "Domains" + ], + "summary": "Updates a domain.", + "description": "Updates a domain. Only supplied fields will be updated, existing won't be affected.", + "operationId": "putDomain", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/domain-put" + }, + + "responses": { + "200": { + "description": "OK" + }, + "401": { + "description": "API key is missing or invalid." + }, + "404": { + "description": "Domain not found." + } + }, + "security": [ + { + "Authorization": [] + } + ] + }, + "delete": { + "tags": [ + "Domains" + ], + "summary": "Deletes a domain.", + "description": "Deletes a domain.", + "operationId": "73c6c14e6d84f759d2e09029cb7ab2be", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "$ref": "#/components/responses/204-no-content" + }, + "401": { + "$ref": "#/components/responses/401-unauthorized" + }, + "404": { + "$ref": "#/components/responses/404-not-found" + } + }, "security": [ + { + "Authorization": [] + } + ] + } + }, + "/domains/name/{name}": { + "get": { + "tags": [ + "Domains" + ], + "summary": "Returns a single domain by name.", + "description": "Returns information of a single domain specified by its domain name.", + "operationId": "getSingleDomainByName", + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/200-ok" + }, + "400": { + "$ref": "#/components/responses/400-bad-request" + }, + "401": { + "$ref": "#/components/responses/401-unauthorized" + } + } + } + } + }, + "components": { + "securitySchemes": { + "Authorization": { + "type": "apiKey", + "description": "Authentication Token", + "name": "X-API-Key", + "in": "header" + } + }, + "requestBodies": { + "dyndns-put": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dyndns" + }, + "example": { + "a": "1.2.3.4", + "aaaa": "1bad::babe" + } + } + } + }, + "domain-post": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/domain" + } + } + } + }, + "domain-put": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/domain" + }, + "example": { + "name": "example.com", + "panel_id": "8" + } + } + } + } + }, + "responses": { + "ping": { + "description": "OK.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ping" + } + } + } + }, + "domain": { + "description": "OK.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/domain" + } + } + } + }, + "domain-array": { + "description": "OK.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/domain" + } + } + } + } + }, + "200-ok": { + "description": "OK." + }, + "200-ok-updated": { + "description": "OK.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/created" + }, + "example": { + "id": 8 + } + } + } + }, + "201-created": { + "description": "Created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/created" + }, + "example": { + "id": 8 + } + } + } + }, + "204-no-content": { + "description": "No content." + }, + "400-bad-request": { + "description": "Bad request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + }, + "example": { + "code": "400 Bad Request", + "message": "Invalid request body." + } + } + } + }, + "401-unauthorized": { + "description": "Unauthorized.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + }, + "example": { + "code": "401 Unauthorized", + "message": "API key is missing or invalid." + } + } + } + }, + "404-not-found": { + "description": "The specified resource was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + }, + "example": { + "code": "404 Not Found", + "message": "The specified resource was not found." + } + } + } + } + }, + "schemas": { + "ping": { + "type": "object", + "properties": { + "response": { + "type": "string", + "example": "pong" + } + } + }, + "dyndns": { + "type": "object", + "properties": { + "a": { + "type": "string", + "example": "1.2.3.4" + }, + "aaaa": { + "type": "string", + "example": "1bad::babe" + } + } + + }, + "domain": { + "description": "Representation of a domain.\n", + "type": "object", + "properties": { + "id": { + "type": "integer", + "readOnly": true, + "description": "The ID of the domain.", + "example": 8 + }, + "name": { + "type": "string", + "description": "The ASCII representation of the domain.", + "example": "example.com" + }, + "panel_id": { + "type": "integer", + "description": "The KeyHelp Panel ID. Either this or at least one IP address is required", + "example": 4 + }, + "a": { + "type": "string", + "readOnly": true, + "description": "The IPv4 address.", + "example": "12.13.14.15" + }, + "aaaa": { + "type": "string", + "readOnly": true, + "description": "The IPv6 address.", + "example": "1bad::babe" + } + } + }, + "created": { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + } + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "code", + "message#" + ] + } + } + } +} diff --git a/public/openapi/bootstrap.php b/public/openapi/bootstrap.php index e69de29..351381c 100644 --- a/public/openapi/bootstrap.php +++ b/public/openapi/bootstrap.php @@ -0,0 +1,6 @@ +baseDir = dirname(path: __DIR__, levels: 2) . '/'; + $this->commandGroupContainer = (new CommandGroupContainer()) ->addCommandGroup(commandGroup: (new CommandGroup(name: 'apikeys', description: 'API keys to access this bindAPI')) ->addCommand(command: new Command( @@ -120,6 +123,12 @@ class CLIController $this->checkPanels(); }, optionalParameters: ['ID', 'fix=xes'])) + ->addCommand(command: new Command( + name: 'nameserver', + callback: function () { + $this->checkNameserver(); + }, + description: 'Validate setting for this panel')) ->addCommand(command: new Command( name: 'domains', callback: function () { @@ -168,7 +177,7 @@ class CLIController $this->panelsCreate(); }, mandatoryParameters: ['name'], - optionalParameters: ['A=', 'AAAA=', 'apikey='])) + optionalParameters: ['A=', 'AAAA=', 'apikey=', 'self='])) ->addCommand(command: new Command( name: 'update', callback: function () { @@ -348,7 +357,6 @@ class CLIController $this->logger->debug(message: "showUsage()"); $debug = $this->configController->getConfig(configKey: 'debug'); - echo COLOR_DEFAULT . ')' . PHP_EOL; echo COLOR_YELLOW . 'Usage:' . PHP_EOL; echo COLOR_DEFAULT . "\t./bin/console {options} {arguments}" . PHP_EOL . PHP_EOL; @@ -952,6 +960,7 @@ class CLIController } + // FIXME check validation state of panel and nameserver keys var_dump($prefix, $key); echo 'Length of prefix: ' . strlen($prefix) . PHP_EOL; echo 'Length of key: ' . strlen($key) . PHP_EOL; @@ -1132,8 +1141,6 @@ class CLIController if ($result['data'] == 'pong') { echo ' ' . COLOR_GREEN . $result['data']; } else { - var_dump($result); - die; echo COLOR_BLUE . ' xxskip' . COLOR_DEFAULT; if (!$this->configController->getConfig(configKey: 'quiet')) { echo ' ' . $result['data']; @@ -1549,28 +1556,72 @@ class CLIController exit(0); } + $self = $this->arguments['self'] ?? ''; + if ($this->nameserverRepository->findByName(name: $name)) { echo "Nameserver: $name already exists." . PHP_EOL; exit(1); } else { - $nameserver = new Nameserver(name: $name, a: $a, aaaa: $aaaa, passphrase: $apikey); + $nameserver = new Nameserver(name: $name, a: $a, aaaa: $aaaa, passphrase: $apikey, self: $self); $result = $this->nameserverRepository->insert(nameserver: $nameserver); echo 'Nameserver ' . COLOR_YELLOW . $name . COLOR_DEFAULT . ' has been created with id ' . COLOR_YELLOW . $result . COLOR_DEFAULT . PHP_EOL; + $this->createOpenAPIBootstrap(); exit(0); } } +// FIXME add method create bootstrap php, add it to create, delete and update nameservers + + public function createOpenAPIBootstrap() + { + $basePath = $this->baseDir . '/public/openapi/'; + + $bootStrapFile = $basePath . 'bootstrap.php'; + $allNameservers = $this->nameserverRepository->findAll(); + + $nameservers = []; + $defaultNS = ''; + + foreach ($allNameservers as $ns) { + $nameservers[] = $ns->getName(); + if ($ns->getSelf() === 'yes') { + $defaultNS = $ns->getName(); + } + } + + // Improvement in generating file content + $nameserverList = implode(separator: "', '", array: $nameservers); + + $currentDBVersion = $this->settingsRepository->findByName(name: 'version'); + $dbVersion = json_decode(json: $currentDBVersion); + $versionSting = $dbVersion->version->major . '.' . $dbVersion->version->minor . '.' . $dbVersion->version->patch; + + + $fileContent = "quiet) { + echo 'Error: ' . $e->getMessage() . PHP_EOL; + } + } + shell_exec(command: $this->baseDir . 'vendor/bin/openapi src/Controller/RequestController.php -b public/openapi/bootstrap.php -o public/openapi/bindapi.json'); + } - /** - * @return void - */ function nameserversList(): void { $nameservers = $this->nameserverRepository->findAll(); if (!empty($nameservers)) { echo 'All available nameservers:' . PHP_EOL; $table = new ConsoleTable(); - $table->setHeaders(content: ['ID', 'Name', 'A', 'AAAA', 'API Key']); + $table->setHeaders(content: ['ID', 'Name', 'A', 'AAAA', 'API Key', 'self']); + foreach ($nameservers as $nameserver) { $row = []; $row[] = $nameserver->getId(); @@ -1578,6 +1629,7 @@ class CLIController $row[] = $nameserver->getA(); $row[] = $nameserver->getAaaa(); $row[] = $nameserver->getApikeyPrefix(); + $row[] = $nameserver->getSelf(); $table->addRow(data: $row); } $table->setPadding(value: 2); @@ -1599,6 +1651,8 @@ class CLIController $a = $this->arguments['a'] ?? ''; $aaaa = $this->arguments['aaaa'] ?? ''; $apikey = $this->arguments['apikey'] ?? ''; + $self = $this->arguments['self'] ?? ''; + if ($id == 0) { echo 'An ID is required.' . PHP_EOL; @@ -1610,15 +1664,20 @@ class CLIController } if ($apikey) { - $nameserver = new Nameserver(name: $name, id: intval(value: $id), a: $a, aaaa: $aaaa, passphrase: $apikey); + $nameserver = new Nameserver(name: $name, id: intval(value: $id), a: $a, aaaa: $aaaa, passphrase: $apikey, self: $self); } else { - $nameserver = new Nameserver(name: $name, id: intval(value: $id), a: $a, aaaa: $aaaa); + $nameserver = new Nameserver(name: $name, id: intval(value: $id), a: $a, aaaa: $aaaa, self: $self); } if ($this->nameserverRepository->update(nameserver: $nameserver) !== false) { - echo 'Nameserver ' . COLOR_YELLOW . $id . COLOR_DEFAULT . ' has been updated.' . PHP_EOL; + $this->createOpenAPIBootstrap(); + if (!$this->quiet) { + echo 'Nameserver ' . COLOR_YELLOW . $id . COLOR_DEFAULT . ' has been updated.' . PHP_EOL; + } } else { - echo 'Error while updating nameserver ' . COLOR_YELLOW . $id . '.' . PHP_EOL; + if (!$this->quiet) { + echo 'Error while updating nameserver ' . COLOR_YELLOW . $id . '.' . PHP_EOL; + } } } @@ -1628,21 +1687,31 @@ class CLIController function nameserversDelete(): void { if (empty($this->arguments[1])) { - echo "You need to supply an ID." . PHP_EOL; + if (!$this->quiet) { + echo "You need to supply an ID." . PHP_EOL; + } exit(1); } $id = intval(value: $this->arguments[1] ?? 0); if ($id == 0) { - echo 'Nameserver with ID ' . COLOR_YELLOW . $id . COLOR_DEFAULT . ' not found.' . PHP_EOL; + if (!$this->quiet) { + echo 'Nameserver with ID ' . COLOR_YELLOW . $id . COLOR_DEFAULT . ' not found.' . PHP_EOL; + } exit(1); } if (!$this->nameserverRepository->findByID(id: $id)) { - echo 'There is no nameserver with ID ' . COLOR_YELLOW . $id . COLOR_DEFAULT . '.' . PHP_EOL; + if (!$this->quiet) { + echo 'There is no nameserver with ID ' . COLOR_YELLOW . $id . COLOR_DEFAULT . '.' . PHP_EOL; + } exit(1); } $this->nameserverRepository->delete(id: $id); - echo 'The nameserver with ID ' . COLOR_YELLOW . $id . COLOR_DEFAULT . ' has been deleted.' . PHP_EOL; + $this->createOpenAPIBootstrap(); + + if (!$this->quiet) { + echo 'The nameserver with ID ' . COLOR_YELLOW . $id . COLOR_DEFAULT . ' has been deleted.' . PHP_EOL; + } } @@ -2066,7 +2135,7 @@ class CLIController $update = true; } - $composerFile = dirname(path: __DIR__, levels: 2) . DIRECTORY_SEPARATOR . 'composer.json'; + $composerFile = $this->baseDir . DIRECTORY_SEPARATOR . 'composer.json'; $composerJson = json_decode(json: file_get_contents(filename: $composerFile), associative: false); $fileVersion = $composerJson->version; @@ -2087,7 +2156,7 @@ class CLIController $this->settingsRepository->set(name: 'buildnumber', value: $fileBuildNumber); } $currentDBVersion = $this->settingsRepository->findByName(name: 'version'); - $dbVersion = json_decode($currentDBVersion); + $dbVersion = json_decode(json: $currentDBVersion); $currentDBBuildnumber = $this->settingsRepository->findByName(name: 'buildnumber'); echo "DB version:\t\t"; @@ -2193,6 +2262,34 @@ class CLIController } + private function checkNameserver() + { + $self = $this->panelRepository->findSelf(); + + $nameserverCount = count($self); + + if ($nameserverCount != 1) { + if ($nameserverCount == 0) { + if (!$this->quiet) { + echo 'No nameserver marked as this server.' . PHP_EOL; + echo 'The setting is used to generate the default nameserver in SwaggerUI.' . PHP_EOL; + echo 'Use ' . COLOR_YELLOW . 'nameservers:update self=yes ' . COLOR_DEFAULT . 'to mark this nameserver.' . PHP_EOL; + exit(1); + } + } else { + if (!$this->quiet) { + echo 'Only one nameserver should be marked as self.' . PHP_EOL; + echo 'The setting is used to generate the default nameserver in SwaggerUI.' . PHP_EOL; + echo 'Use ' . COLOR_YELLOW . 'nameservers:update self=no ' . COLOR_DEFAULT . 'to remove the stale nameserver' . PHP_EOL; + } + } + } else { + if (!$this->quiet) { + echo 'Only one nameserver is designated as our host' . COLOR_GREEN . ' OK' . COLOR_DEFAULT . PHP_EOL; + } + } + } + private function cronRun() { $this->logger->info(message: 'cronRun()'); diff --git a/src/Controller/RequestController.php b/src/Controller/RequestController.php index 78c3602..cd8c208 100644 --- a/src/Controller/RequestController.php +++ b/src/Controller/RequestController.php @@ -17,13 +17,9 @@ use OpenApi\Generator; use UnhandledMatchError; use function Symfony\Component\String\s; - // TODO attributes for swaggerUI -/** - * - */ -#[OAT\Info(version: '1.0.9', title: 'bindAPI')] +#[OAT\Info(version: VERSION, title: 'bindAPI')] #[OAT\Server( url: "{schema}://{hostname}/api", description: "The bindAPI URL.", @@ -35,8 +31,8 @@ use function Symfony\Component\String\s; ), new OAT\ServerVariable( serverVariable: 'hostname', - default: 'ns1.24unix.net', - enum: ['ns1.24unix.net', 'ns2.24unix.net', 'ns3.24unix.net', 'ns4.24unix.net'] + default: DEFAULT_NS, + enum: NAMESERVERS ) ] )] @@ -104,7 +100,7 @@ class RequestController responses: [ new OAT\Response( response: 200, - description: 'x.y.y' + description: 'x.y.z, aka major, minor, patch' ), new OAT\Response( response: 401, @@ -128,7 +124,7 @@ class RequestController path: '/domains', operationId: 'getAllDomains', description: 'Returns a list of all domains on this server.', - summary: 'Listing all domains.', + summary: 'List all domains.', security: [ ['Authorization' => []] ], @@ -635,17 +631,17 @@ class RequestController return $host; } - private function apiDoc(): void - { - $srcDir = dirname(path: __DIR__); - $requestControllerPath = $srcDir . '/Controller/RequestController.php'; - - $openApi = Generator::scan(sources: [$requestControllerPath]); - header(header: 'Content-Type: application/json'); - - echo $openApi->toJson(); - exit(0); - } +// private function apiDoc(): void +// { +// $srcDir = dirname(path: __DIR__); +// $requestControllerPath = $srcDir . '/Controller/RequestController.php'; +// +// $openApi = Generator::scan(sources: [$requestControllerPath]); +// header(header: 'Content-Type: application/json'); +// +// echo $openApi->toJson(); +// exit(0); +// } public function __construct( private readonly ApikeyRepository $apikeyRepository, diff --git a/src/Entity/Nameserver.php b/src/Entity/Nameserver.php index 62aa325..872266e 100644 --- a/src/Entity/Nameserver.php +++ b/src/Entity/Nameserver.php @@ -12,7 +12,6 @@ use SodiumException; * */ #[OAT\Schema(schema: 'nameserver')] - class Nameserver { /** @@ -31,8 +30,10 @@ class Nameserver private string $aaaa = '', private readonly string $passphrase = '', private string $apikey = '', - private string $apikeyPrefix = '') - { + private string $apikeyPrefix = '', + private string $self = 'no' + ) + { if ($this->passphrase) { $configController = new ConfigController(quiet: false); $encryptionController = new EncryptionController(); @@ -50,6 +51,16 @@ class Nameserver } + public function getSelf(): string + { + return $this->self; + } + + public function setSelf(string $self): void + { + $this->self = $self; + } + /** * @return string */ @@ -67,90 +78,90 @@ class Nameserver } - /** - * @return string - */ - #[OAT\Property(type: 'string')] - public function getA(): string - { - return $this->a; - } + /** + * @return string + */ + #[OAT\Property(type: 'string')] + public function getA(): string + { + return $this->a; + } - /** - * @param string $a - */ - public function setA(string $a): void - { - $this->a = $a; - } + /** + * @param string $a + */ + public function setA(string $a): void + { + $this->a = $a; + } - /** - * @return string - */ - #[OAT\Property(type: 'string')] - public function getAaaa(): string - { - return $this->aaaa; - } + /** + * @return string + */ + #[OAT\Property(type: 'string')] + public function getAaaa(): string + { + return $this->aaaa; + } - /** - * @param string $aaaa - */ - public function setAaaa(string $aaaa): void - { - $this->aaaa = $aaaa; - } + /** + * @param string $aaaa + */ + public function setAaaa(string $aaaa): void + { + $this->aaaa = $aaaa; + } - /** - * @return string - */ - #[OAT\Property(type: 'string')] - public function getApikey(): string - { - return $this->apikey; - } + /** + * @return string + */ + #[OAT\Property(type: 'string')] + public function getApikey(): string + { + return $this->apikey; + } - /** - * @param string $apikey - */ - public function setApikey(string $apikey): void - { - $this->apikey = $apikey; - } + /** + * @param string $apikey + */ + public function setApikey(string $apikey): void + { + $this->apikey = $apikey; + } - /** - * @return int - */ - #[OAT\Property(type: 'int')] - public function getId(): int - { - return $this->id; - } + /** + * @return int + */ + #[OAT\Property(type: 'int')] + public function getId(): int + { + return $this->id; + } - /** - * @param int $id - */ - public function setId(int $id): void - { - $this->id = $id; - } + /** + * @param int $id + */ + public function setId(int $id): void + { + $this->id = $id; + } - /** - * @return string - */ - #[OAT\Property(type: 'string')] - public function getName(): string - { - return $this->name; - } + /** + * @return string + */ + #[OAT\Property(type: 'string')] + public function getName(): string + { + return $this->name; + } - /** - * @param string $name - */ - public function setName(string $name): void - { - $this->name = $name; - } + /** + * @param string $name + */ + public function setName(string $name): void + { + $this->name = $name; + } /** * @return string diff --git a/src/Repository/NameserverRepository.php b/src/Repository/NameserverRepository.php index 67ad28c..cf2c0dd 100644 --- a/src/Repository/NameserverRepository.php +++ b/src/Repository/NameserverRepository.php @@ -26,7 +26,7 @@ class NameserverRepository { $nameservers = []; $sql = " - SELECT id, name, a, aaaa, apikey, apikey_prefix + SELECT id, name, a, aaaa, apikey, apikey_prefix, self FROM " . DatabaseConnection::TABLE_NAMESERVERS . " ORDER BY name"; @@ -34,7 +34,7 @@ class NameserverRepository $statement = $this->databaseConnection->getConnection()->prepare(query: $sql); $statement->execute(); while ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) { - $nameserver = new Nameserver(name: $result['name'], id: $result['id'], a: $result['a'], aaaa: $result['aaaa'], apikey: $result['apikey'], apikeyPrefix: $result['apikey_prefix']); + $nameserver = new Nameserver(name: $result['name'], id: $result['id'], a: $result['a'], aaaa: $result['aaaa'], apikey: $result['apikey'], apikeyPrefix: $result['apikey_prefix'], self: $result['self']); $nameservers[] = $nameserver; } return $nameservers; @@ -50,7 +50,7 @@ class NameserverRepository public function findFirst(): ?Nameserver { $sql = " - SELECT id, name, a, aaaa, apikey, apikey_prefix + SELECT id, name, a, aaaa, apikey, apikey_prefix, self FROM " . DatabaseConnection::TABLE_NAMESERVERS . " ORDER BY name"; @@ -58,7 +58,7 @@ class NameserverRepository $statement = $this->databaseConnection->getConnection()->prepare(query: $sql); $statement->execute(); $result = $statement->fetch(mode: PDO::FETCH_ASSOC); - return new Nameserver(name: $result['name'], id: $result['id'], a: $result['a'], aaaa: $result['aaaa'], apikey: $result['apikey'], apikeyPrefix: $result['apikey_prefix']); + return new Nameserver(name: $result['name'], id: $result['id'], a: $result['a'], aaaa: $result['aaaa'], apikey: $result['apikey'], apikeyPrefix: $result['apikey_prefix'], self: $result['self']); } catch (PDOException $e) { exit($e->getMessage()); } @@ -73,7 +73,7 @@ class NameserverRepository public function findByID(int $id): ?Nameserver { $sql = " - SELECT id, name, a, aaaa, apikey, apikey_prefix + SELECT id, name, a, aaaa, apikey, apikey_prefix, self FROM . " . DatabaseConnection::TABLE_NAMESERVERS . " WHERE id = :id"; @@ -82,7 +82,7 @@ class NameserverRepository $statement->bindParam(param: ':id', var: $id); $statement->execute(); if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) { - return new Nameserver(name: $result['name'], a: $result['a'], aaaa: $result['aaaa'], apikey: $result['apikey'], apikeyPrefix: $result['apikey_prefix']); + return new Nameserver(name: $result['name'], a: $result['a'], aaaa: $result['aaaa'], apikey: $result['apikey'], apikeyPrefix: $result['apikey_prefix'], self: $result['self']); } else { return null; } @@ -100,7 +100,7 @@ class NameserverRepository public function findByName(string $name): ?Nameserver { $sql = " - SELECT id, name, a, aaaa, apikey, apikey_prefix + SELECT id, name, a, aaaa, apikey, apikey_prefix, self FROM " . DatabaseConnection::TABLE_NAMESERVERS . " WHERE name = :name"; @@ -109,7 +109,7 @@ class NameserverRepository $statement->bindParam(param: ':name', var: $name); $statement->execute(); if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) { - return new Nameserver(name: $result['name'], a: $result['a'], aaaa: $result['aaaa'], apikey: $result['apikey'], apikeyPrefix: $result['apikey_prefix']); + return new Nameserver(name: $result['name'], a: $result['a'], aaaa: $result['aaaa'], apikey: $result['apikey'], apikeyPrefix: $result['apikey_prefix'], self: $result['self']); } else { return null; } @@ -131,11 +131,12 @@ class NameserverRepository $aaaa = $nameserver->getAaaa(); $apikey = $nameserver->getApikey(); $apikeyPrefix = $nameserver->getApikeyPrefix(); + $self = $nameserver->getSelf(); $sql = " - INSERT INTO " . DatabaseConnection::TABLE_NAMESERVERS . " (name, a, aaaa, apikey, apikey_prefix) - VALUES (:name, :a, :aaaa, :apikey, :apikey_prefix)"; + INSERT INTO " . DatabaseConnection::TABLE_NAMESERVERS . " (name, a, aaaa, apikey, apikey_prefix, self) + VALUES (:name, :a, :aaaa, :apikey, :apikey_prefix, :self)"; try { $statement = $this->databaseConnection->getConnection()->prepare(query: $sql); @@ -144,6 +145,7 @@ class NameserverRepository $statement->bindParam(param: ':aaaa', var: $aaaa); $statement->bindParam(param: ':apikey', var: $apikey); $statement->bindParam(param: ':apikey_prefix', var: $apikeyPrefix); + $statement->bindParam(param: ':self', var: $self); $statement->execute(); return intval(value: $this->databaseConnection->getConnection()->lastInsertId()); @@ -166,6 +168,7 @@ class NameserverRepository $apikey = $nameserver->getApikey(); $apikeyPrefix = $nameserver->getApikeyPrefix(); $passphrase = $nameserver->getPassphrase(); + $self =$nameserver->getSelf(); $current = $this->findByID(id: $id); @@ -185,6 +188,10 @@ class NameserverRepository $apikeyPrefix = $current->getApikeyPrefix(); } + if (empty($self)) { + $self = $current->getSelf(); + } + $sql = " UPDATE " . DatabaseConnection::TABLE_NAMESERVERS . " SET @@ -192,7 +199,8 @@ class NameserverRepository a = :a, aaaa = :aaaa, apikey = :apikey, - apikey_prefix = :apikey_prefix + apikey_prefix = :apikey_prefix, + self = :self WHERE id = :id"; try { @@ -203,6 +211,7 @@ class NameserverRepository $statement->bindParam(param: 'aaaa', var: $aaaa); $statement->bindParam(param: 'apikey', var: $apikey); $statement->bindParam(param: 'apikey_prefix', var: $apikeyPrefix); + $statement->bindParam(param: 'self', var: $self); $statement->execute(); try { sodium_memzero(string: $apikey); diff --git a/src/Util/Console.php b/src/Util/Console.php index 5727671..f4d9996 100644 --- a/src/Util/Console.php +++ b/src/Util/Console.php @@ -5,7 +5,8 @@ use App\Service\BindAPI; error_reporting(error_level: E_ALL & ~E_DEPRECATED); if (!is_file(filename: dirname(path: __DIR__, levels: 2) . '/vendor/autoload.php')) { - exit('Required runtime components are missing. Try running "composer install".' . PHP_EOL); + echo 'Required runtime components are missing. Try running "' . COLOR_YELLOW . 'composer install' . COLOR_DEFAULT . '".' . PHP_EOL; + exit(1); } require dirname(path: __DIR__, levels: 2) . '/vendor/autoload.php';