diff --git a/tests/Fixture/InboxFixture.php b/tests/Fixture/InboxFixture.php new file mode 100644 index 0000000..40303b2 --- /dev/null +++ b/tests/Fixture/InboxFixture.php @@ -0,0 +1,61 @@ +records = [ + [ + 'id' => self::INBOX_USER_REGISTRATION_ID, + 'uuid' => $faker->uuid(), + 'scope' => 'User', + 'action' => 'Registration', + 'title' => 'User account creation requested for foo@bar.com', + 'origin' => '::1', + 'comment' => null, + 'description' => 'Handle user account for this cerebrate instance', + 'user_id' => UsersFixture::USER_ADMIN_ID, + 'data' => [ + 'email' => 'foo@bar.com', + 'password' => '$2y$10$dr5C0MWgBx1723yyws0HPudTqHz4k8wJ1PQ1ApVkNuH64LuZAr\/ve', + ], + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::INBOX_INCOMING_CONNECTION_REQUEST_ID, + 'uuid' => $faker->uuid(), + 'scope' => 'LocalTool', + 'action' => 'IncomingConnectionRequest', + 'title' => 'Request for MISP Inter-connection', + 'origin' => 'http://127.0.0.1', + 'comment' => null, + 'description' => 'Handle Phase I of inter-connection when another cerebrate instance performs the request.', + 'user_id' => UsersFixture::USER_ORG_ADMIN_ID, + 'data' => [ + 'connectorName' => 'MispConnector', + 'cerebrateURL' => 'http://127.0.0.1', + 'local_tool_id' => 1, + 'remote_tool_id' => 1, + ], + 'created' => $faker->dateTime()->getTimestamp(), + 'modified' => $faker->dateTime()->getTimestamp() + ], + ]; + parent::init(); + } +} diff --git a/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php b/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php new file mode 100644 index 0000000..30a44c2 --- /dev/null +++ b/tests/TestCase/Api/Inbox/CreateInboxEntryApiTest.php @@ -0,0 +1,82 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testAddUserRegistrationInbox(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + + // to avoid $this->request->clientIp() to return null + $_SERVER['REMOTE_ADDR'] = '::1'; + + $url = sprintf("%s/%s/%s", self::ENDPOINT, 'User', 'Registration'); + $this->post( + $url, + [ + 'email' => 'john@example.com', + 'password' => 'Password12345!' + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains('"email": "john@example.com"'); + $this->assertDbRecordExists( + 'Inbox', + [ + 'id' => 3, // hacky, but `data` is json string cannot verify the value because of the hashed password + 'scope' => 'User', + 'action' => 'Registration', + ] + ); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'post'); + } + + public function testAddUserRegistrationInboxNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + + $url = sprintf("%s/%s/%s", self::ENDPOINT, 'User', 'Registration'); + $this->post( + $url, + [ + 'email' => 'john@example.com', + 'password' => 'Password12345!' + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('Inbox', ['id' => 3]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'post'); + } +} diff --git a/tests/TestCase/Api/Inbox/IndexInboxApiTest.php b/tests/TestCase/Api/Inbox/IndexInboxApiTest.php new file mode 100644 index 0000000..00c2cfd --- /dev/null +++ b/tests/TestCase/Api/Inbox/IndexInboxApiTest.php @@ -0,0 +1,46 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testIndexInbox(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', InboxFixture::INBOX_USER_REGISTRATION_ID)); + $this->assertResponseContains(sprintf('"id": %d', InboxFixture::INBOX_INCOMING_CONNECTION_REQUEST_ID)); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); + } +} diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index d74ad68..59a0ecf 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -17,6 +17,8 @@ tags: description: "Organisations can be equivalent to legal entities or specific individual teams within such entities. Their purpose is to relate individuals to their affiliations and for release control of information using the Trust Circles." - name: Tags description: "Tags can be attached to entity to quickly classify them, allowing further filtering and searches." + - name: Inbox + description: "Inbox messages represent A list of requests to be manually processed." paths: /api/v1/individuals/index: @@ -369,6 +371,42 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" + /api/v1/inbox/index: + get: + summary: "Get inbox list" + operationId: getinbox + tags: + - Inbox + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/InboxListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/inbox/createEntry/User/Registration: + post: + summary: "Create user registration inbox entry" + operationId: createInboxEntry + tags: + - Inbox + requestBody: + $ref: "#/components/requestBodies/CreateUserRegistrationInboxEntryRequest" + responses: + "200": + $ref: "#/components/responses/CreateUserRegistrationInboxEntryResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + components: schemas: # General @@ -615,6 +653,109 @@ components: perm_org_admin: type: boolean + # Inbox + InboxScope: + type: string + enum: + - "User" + - "LocalTool" + + InboxAction: + type: string + enum: + - "Registration" + - "IncomingConnectionRequest" + - "AcceptedRequest" + - "DeclinedRequest" + + InboxTitle: + type: string + + InboxOrigin: + type: string + + InboxComment: + type: string + nullable: true + + InboxDescription: + type: string + nullable: true + + Inbox: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + scope: + $ref: "#/components/schemas/InboxScope" + action: + $ref: "#/components/schemas/InboxAction" + title: + $ref: "#/components/schemas/InboxTitle" + origin: + $ref: "#/components/schemas/InboxOrigin" + comment: + $ref: "#/components/schemas/InboxComment" + description: + $ref: "#/components/schemas/InboxDescription" + user_id: + $ref: "#/components/schemas/ID" + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + + UserRegistrationInbox: + type: object + allOf: + - $ref: "#/components/schemas/Inbox" + - type: object + properties: + data: + type: object + properties: + email: + type: string + format: email + password: + type: string + user: + $ref: "#/components/schemas/User" + local_tool_connector_name: + type: string + nullable: true + + IncomingConnectionRequestInbox: + type: object + allOf: + - $ref: "#/components/schemas/Inbox" + - type: object + properties: + data: + type: object + properties: + connectorName: + type: string + enum: + - "MispConnector" + cerebrateURL: + type: string + example: "http://192.168.0.1" + local_tool_id: + type: integer + remote_tool_id: + type: integer + + InboxList: + type: array + items: + anyOf: + - $ref: "#/components/schemas/UserRegistrationInbox" + - $ref: "#/components/schemas/IncomingConnectionRequestInbox" + # Errors ApiError: type: object @@ -685,7 +826,7 @@ components: parameters: individualId: - name: userId + name: individualId in: path description: "Numeric ID of the User" required: true @@ -876,6 +1017,20 @@ components: description: "Stringified JSON array of the tag names to remove." example: '["red"]' + # Inbox + CreateUserRegistrationInboxEntryRequest: + description: "Create user registration inbox entry request" + content: + application/json: + schema: + type: object + properties: + email: + type: string + format: email + password: + type: string + responses: # Individuals IndividualResponse: @@ -937,6 +1092,52 @@ components: schema: $ref: "#/components/schemas/TagList" + # Inbox + UserRegistrationInboxResponse: + description: "User registration inbox response" + content: + application/json: + schema: + $ref: "#/components/schemas/UserRegistrationInbox" + + IncomingConnectionRequestInboxResponse: + description: "Incoming connection request inbox response" + content: + application/json: + schema: + $ref: "#/components/schemas/IncomingConnectionRequestInbox" + + InboxListResponse: + description: "Inbox list response" + content: + application/json: + schema: + $ref: "#/components/schemas/InboxList" + + CreateUserRegistrationInboxEntryResponse: + description: "Inbox response" + content: + application/json: + schema: + type: object + properties: + data: + allOf: + - $ref: "#/components/schemas/UserRegistrationInbox" + - properties: + local_tool_connector_name: + type: string + nullable: true + success: + type: boolean + message: + type: string + example: "User account creation requested. Please wait for an admin to approve your account." + errors: + type: array + items: + type: object + # Errors ApiErrorResponse: description: "Unexpected API error"