diff --git a/code/API_definitions/sim-swap-subscriptions.yaml b/code/API_definitions/sim-swap-subscriptions.yaml index b45f5bd..612af37 100644 --- a/code/API_definitions/sim-swap-subscriptions.yaml +++ b/code/API_definitions/sim-swap-subscriptions.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: Sim Swap Subscriptions description: | - This API provides the customer with the ability to subscribe to event related to a sim swap operation performed on an associated phone number. + This API provides the customer with the ability to subscribe to events related to a SIM swap operation performed on an associated phone number. # Introduction @@ -16,18 +16,18 @@ info: # API Functionality The API exposes following capabilities: - * **/subscriptions**: This endpoint has two operations, one that allows to retrieve the list of Sim Swap event subscriptions and another one to create new event subscriptions. + * **/subscriptions**: This endpoint has two operations, one that allows to retrieve the list of Sim Swap event subscriptions and another one to create new event subscriptions. The list operation supports pagination via `page` and `perPage` query parameters returning a `SubscriptionList` with subscription items and pagination headers. Creating new event subscriptions requires a sink, protocol, event types, and configuration; sink credentials are optional. * **/subscriptions/{subscriptionId}**: This endpoint also has two operations, one that allows to retrieve a particular event subscription by id and another one to delete an event subscription also using the id. ## Sim Swap notification subscription - Theses endpoints allow to manage notification subscription on Sim Swap event. - The CAMARA subscription model is detailed in the CAMARA API design guideline document and follows Cloudevents specification. + These endpoints allow to manage notification subscription on Sim Swap event. + The CAMARA subscription model is detailed in the CAMARA API design guideline document and follows Cloudevents specification. Sink credentials support the ACCESSTOKEN and PRIVATE_KEY_JWT credential types for securing webhook delivery. - It is mandatory in the subscription to provide the event `types` subscribed are several are managed in this API. + The `types` field is mandatory when creating a subscription, per the CAMARA subscription model, regardless of how many event types an API defines. - Only one event ``types`` is managed for this API: - - ``org.camaraproject.sim-swap-subscriptions.v0.swapped`` - Event triggered when a sim swap occurs on the associated phoneNumber + Only one event `types` is managed for this API: + - `org.camaraproject.sim-swap-subscriptions.v0.swapped` - Event triggered when a SIM swap occurs on the associated phone number Note: Additionally to these list, ``org.camaraproject.sim-swap-subscriptions.v0.subscription-ended`` notification `types` is sent when the subscription ends. @@ -113,7 +113,7 @@ externalDocs: description: Product documentation at CAMARA url: https://github.com/camaraproject/SimSwap servers: - - url: '{apiRoot}/sim-swap-subscriptions/vwip' + - url: "{apiRoot}/sim-swap-subscriptions/vwip" variables: apiRoot: default: http://localhost:9091 @@ -127,7 +127,7 @@ paths: post: tags: - Sim Swap Subscription - summary: 'Create a sim swap event subscription for a phone number' + summary: "Create a sim swap event subscription for a phone number" description: Create a sim swap event subscription for a phone number operationId: createSimSwapSubscription parameters: @@ -143,9 +143,9 @@ paths: $ref: "#/components/schemas/SubscriptionRequest" examples: SUBSCRIPTION_REQUEST: - $ref: '#/components/examples/SUBSCRIPTION_REQUEST' + $ref: "#/components/examples/SUBSCRIPTION_REQUEST" SUBSCRIPTION_REQUEST_3LEGS: - $ref: '#/components/examples/SUBSCRIPTION_REQUEST_3LEGS' + $ref: "#/components/examples/SUBSCRIPTION_REQUEST_3LEGS" required: true callbacks: notifications: @@ -166,9 +166,9 @@ paths: $ref: "#/components/schemas/NotificationEvent" examples: SWAPPED: - $ref: '#/components/examples/SWAPPED' + $ref: "#/components/examples/SWAPPED" SUBSCRIPTION_ENDS: - $ref: '#/components/examples/SUBSCRIPTION_ENDS' + $ref: "#/components/examples/SUBSCRIPTION_ENDS" responses: "204": description: Successful notification @@ -201,9 +201,9 @@ paths: $ref: "#/components/schemas/Subscription" examples: SUBSCRIPTION_RESPONSE: - $ref: '#/components/examples/SUBSCRIPTION_RESPONSE' + $ref: "#/components/examples/SUBSCRIPTION_RESPONSE" SUBSCRIPTION_RESPONSE_3LEGS: - $ref: '#/components/examples/SUBSCRIPTION_RESPONSE_3LEGS' + $ref: "#/components/examples/SUBSCRIPTION_RESPONSE_3LEGS" "202": description: Request accepted to be processed. It applies for async creation process. headers: @@ -228,7 +228,7 @@ paths: get: tags: - Sim Swap Subscription - summary: 'Retrieve a list of sim swap event subscription' + summary: "Retrieve a list of sim swap event subscription" description: Retrieve a list of sim swap event subscription(s) operationId: retrieveSubscriptionList parameters: @@ -256,9 +256,9 @@ paths: $ref: "#/components/schemas/SubscriptionList" examples: SUBSCRIPTIONS: - $ref: '#/components/examples/SUBSCRIPTIONS' + $ref: "#/components/examples/SUBSCRIPTIONS" SUBSCRIPTIONS_3LEGS: - $ref: '#/components/examples/SUBSCRIPTIONS_3LEGS' + $ref: "#/components/examples/SUBSCRIPTIONS_3LEGS" "400": $ref: "../common/CAMARA_common.yaml#/components/responses/Generic400" "401": @@ -269,7 +269,7 @@ paths: get: tags: - Sim Swap Subscription - summary: 'Retrieve a sim swap event subscription for a phone number' + summary: "Retrieve a sim swap event subscription for a phone number" description: retrieve event subscription information for a given subscription. operationId: retrieveSubscription security: @@ -290,9 +290,9 @@ paths: $ref: "#/components/schemas/Subscription" examples: SUBSCRIPTION: - $ref: '#/components/examples/SUBSCRIPTION_RESPONSE' + $ref: "#/components/examples/SUBSCRIPTION_RESPONSE" SUBSCRIPTION_3LEGS: - $ref: '#/components/examples/SUBSCRIPTION_RESPONSE_3LEGS' + $ref: "#/components/examples/SUBSCRIPTION_RESPONSE_3LEGS" "400": $ref: "../common/CAMARA_event_common.yaml#/components/responses/SubscriptionIdRequired400" "401": @@ -304,7 +304,7 @@ paths: delete: tags: - Sim Swap Subscription - summary: 'Delete a sim swap event subscription' + summary: "Delete a sim swap event subscription" operationId: deleteSubscription description: delete a given event subscription. security: @@ -468,7 +468,7 @@ components: discriminator: propertyName: protocol mapping: - HTTP: '#/components/schemas/HTTPSubscriptionResponse' + HTTP: "#/components/schemas/HTTPSubscriptionResponse" # Future protocol support (not yet used in CAMARA): # MQTT3: "#/components/schemas/MQTTSubscriptionResponse" # MQTT5: "#/components/schemas/MQTTSubscriptionResponse" @@ -586,7 +586,7 @@ components: - org.camaraproject.sim-swap-subscriptions.v0.subscription-ended EventSwapped: - description: event structure for network type change + description: event structure for SIM swap event allOf: - $ref: "#/components/schemas/ApiNotificationEvent" - type: object @@ -703,7 +703,7 @@ components: subscriptionDetail: phoneNumber: "+123456789" subscriptionMaxEvents: 10 - subscriptionExpireTime: '2025-01-17T13:18:23.682Z' + subscriptionExpireTime: "2025-01-17T13:18:23.682Z" SUBSCRIPTION_REQUEST_3LEGS: description: The cloud event for a subscription request created in 3-legs value: @@ -718,7 +718,7 @@ components: - org.camaraproject.sim-swap-subscriptions.v0.swapped config: subscriptionDetail: {} - subscriptionExpireTime: '2025-01-17T13:18:23.682Z' + subscriptionExpireTime: "2025-01-17T13:18:23.682Z" SUBSCRIPTION_RESPONSE: description: Subscription details for an active subscription value: @@ -729,10 +729,10 @@ components: config: subscriptionDetail: phoneNumber: "+123456789" - subscriptionExpireTime: '2025-01-17T13:18:23.682Z' - id: '1119920371' - startsAt: '2024-06-07T16:10:15.302Z' - expiresAt: '2024-06-07T16:10:15.302Z' + subscriptionExpireTime: "2025-01-17T13:18:23.682Z" + id: "1119920371" + startsAt: "2024-06-07T16:10:15.302Z" + expiresAt: "2024-06-07T16:10:15.302Z" status: ACTIVATION_REQUESTED SUBSCRIPTION_RESPONSE_3LEGS: description: Subscription details for an active subscription @@ -743,10 +743,10 @@ components: - org.camaraproject.sim-swap-subscriptions.v0.swapped config: subscriptionDetail: {} - subscriptionExpireTime: '2025-01-17T13:18:23.682Z' - id: '1119920371' - startsAt: '2024-06-07T16:10:15.302Z' - expiresAt: '2024-06-07T16:10:15.302Z' + subscriptionExpireTime: "2025-01-17T13:18:23.682Z" + id: "1119920371" + startsAt: "2024-06-07T16:10:15.302Z" + expiresAt: "2024-06-07T16:10:15.302Z" status: ACTIVATION_REQUESTED SUBSCRIPTIONS: description: A list of API consumer subscriptions. @@ -759,10 +759,10 @@ components: config: subscriptionDetail: phoneNumber: "+123456789" - subscriptionExpireTime: '2025-01-17T13:18:23.682Z' - id: '1119920371' - startsAt: '2024-06-07T16:10:15.302Z' - expiresAt: '2024-06-07T16:10:15.302Z' + subscriptionExpireTime: "2025-01-17T13:18:23.682Z" + id: "1119920371" + startsAt: "2024-06-07T16:10:15.302Z" + expiresAt: "2024-06-07T16:10:15.302Z" status: ACTIVATION_REQUESTED pagination: page: 1 @@ -779,10 +779,10 @@ components: - org.camaraproject.sim-swap-subscriptions.v0.swapped config: subscriptionDetail: {} - subscriptionExpireTime: '2025-01-17T13:18:23.682Z' - id: '1119920371' - startsAt: '2024-06-07T16:10:15.302Z' - expiresAt: '2024-06-07T16:10:15.302Z' + subscriptionExpireTime: "2025-01-17T13:18:23.682Z" + id: "1119920371" + startsAt: "2024-06-07T16:10:15.302Z" + expiresAt: "2024-06-07T16:10:15.302Z" status: ACTIVATION_REQUESTED pagination: page: 1 diff --git a/code/API_definitions/sim-swap.yaml b/code/API_definitions/sim-swap.yaml index bfca8cb..69c762f 100644 --- a/code/API_definitions/sim-swap.yaml +++ b/code/API_definitions/sim-swap.yaml @@ -12,12 +12,13 @@ info: The SIM Swap API can also be used to protect non-automated actions. For example, when a call center expert contacts a user to clarify or confirm a sensitive operation. - This API is used by an application to get information about a mobile line latest SIM swap date. It can be easily integrated and used through this secured API and allows SPs (Service Provider) to get this information an easy & secured way. The API provides management of 3 endpoints answering 3 distinct questions: - An API provider is not expected to support all of them: the age-band operation is an alternative to exposing the exact SIM swap date, and support depends on the provider's available capabilities and commercial use case. + This API is used by an application to get information about a mobile line latest SIM swap date. It can be easily integrated and used through this secured API and allows SPs (Service Provider) to get this information in an easy and secured way. The API provides management of 3 endpoints answering 3 distinct questions: - * When did the last SIM swap occur? - * Has a SIM swap occurred during last n hours? - * How recently did the last SIM swap occur, as a standardized time band? (alternative to the exact date) + * When did the last SIM swap occur? (mandatory `/retrieve-date` operation) + * Has a SIM swap occurred during last n hours? (mandatory `/check` operation) + * How recently did the last SIM swap occur, as a standardized time band? (optional `/retrieve-age-band` operation, an alternative to exposing the exact SIM swap date) + + An API provider is expected to support `/check` and `/retrieve-date`. The `/retrieve-age-band` operation is optional and is an alternative for providers that do not expose the exact SIM swap date; support depends on the provider's available capabilities and commercial use case. A provider that does not implement `/retrieve-age-band` returns `501 NOT_IMPLEMENTED`. # Relevant terms and definitions @@ -269,15 +270,16 @@ paths: occurred, expressed as a time band. This operation is an alternative way to expose SIM swap recency for API providers that - do not expose the exact SIM swap date. It does not return the actual SIM swap date, and - is not expected to be supported in addition to `check` and `retrieve-date`; support - depends on the provider's available capabilities and commercial use case. + do not expose the exact SIM swap date. It does not return the actual SIM swap date. + This operation is optional: a provider need not implement it in addition to the + mandatory `check` and `retrieve-date` operations, and a provider that does not + implement it returns `501 NOT_IMPLEMENTED`. Support depends on the provider's + available capabilities and commercial use case. The returned value is a technical network signal indicating recency. Consuming parties apply their own decisioning outside the API contract. Value `111` indicates the provider positively confirms a SIM swap has never happened and the number has never been ported (a sentinel, not an ordinal - band). This operation is OPTIONAL: a provider that does not implement it returns - `501 NOT_IMPLEMENTED`. A provider that exposes it MUST support the complete standardized + band). A provider that exposes it MUST support the complete standardized band model. If the service is structurally not applicable for the provided identifier — including a valid subscriber for whom no real-time profile is available, or a provider that cannot support the required granularity or determine the band due to retention limitations @@ -348,7 +350,8 @@ components: format: date-time maxLength: 64 nullable: true - description: Timestamp of latest SIM swap performed. It must follow [RFC 3339 (https://datatracker.ietf.org/doc/html/rfc3339#section-5.6) and must have time zone. + description: Timestamp of latest SIM swap performed. It must follow [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.6) and must have time zone. The value is `null` when the provider cannot return it for privacy reasons (the `monitoredPeriod` mechanism applies). + example: "2023-07-03T14:27:08.312+02:00" monitoredPeriod: type: integer format: int32 diff --git a/code/Test_definitions/sim-swap-retrieveSimSwapAgeBand.feature b/code/Test_definitions/sim-swap-retrieveSimSwapAgeBand.feature new file mode 100644 index 0000000..4b048f1 --- /dev/null +++ b/code/Test_definitions/sim-swap-retrieveSimSwapAgeBand.feature @@ -0,0 +1,172 @@ +Feature: CAMARA SIM Swap API, vwip - Operation retrieveSimSwapAgeBand + + # Input to be provided by the implementation to the tester + # + # Testing assets: + # + # References to OAS spec schemas refer to schemas specified in sim_swap.yaml + # + # Retrieve SIM swap age band + + Background: Common retrieveSimSwapAgeBand setup + Given the resource "/sim-swap/vwip/retrieve-age-band" + And the header "Content-Type" is set to "application/json" + And the header "Authorization" is set to a valid access token + And the header "x-correlator" complies with the schema at "#/components/schemas/XCorrelator" + And the request body is set by default to a request body compliant with the schema + + # This first scenario serves as a minimum, not testing any specific age band value + @retrieve_age_band_1_generic_success_scenario + Scenario: Common validations for any success scenario + Given a valid phone number identified by the token or provided in the request body + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 200 + And the response header "Content-Type" is "application/json" + And the response header "x-correlator" has same value as the request header "x-correlator" + And the response body complies with the OAS schema at "/components/schemas/SimSwapAgeBandInfo" + + # Scenarios testing specific age band values + + @retrieve_age_band_2_recent_swap_band_1 + Scenario: Retrieve age band showing recent SIM swap (band 1) + Given a valid phone number identified by the token or provided in the request body + And the SIM for this phone number has been swapped in the last 4 hours + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 200 + And the value of response property "$.simSwapAgeBand" == 1 + + @retrieve_age_band_3_mid_age_swap_band_10 + Scenario: Retrieve age band showing mid-age SIM swap (band 10) + Given a valid phone number identified by the token or provided in the request body + And the SIM for this phone number has been swapped between 14 and 30 days ago + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 200 + And the value of response property "$.simSwapAgeBand" == 10 + + @retrieve_age_band_4_old_swap_band_17 + Scenario: Retrieve age band showing old SIM swap (band 17) + Given a valid phone number identified by the token or provided in the request body + And the SIM for this phone number has been swapped more than 3 years ago + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 200 + And the value of response property "$.simSwapAgeBand" == 17 + + @retrieve_age_band_5_never_swapped_sentinel_111 + Scenario: Retrieve age band showing SIM has never been swapped (sentinel 111) + Given a valid phone number identified by the token or provided in the request body + And the SIM for this phone number has never been swapped + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 200 + And the value of response property "$.simSwapAgeBand" == 111 + + # Scenarios testing different access token types + + @retrieve_age_band_6_3legged_token + Scenario: Retrieve age band using 3-legged access token + Given the header "Authorization" is set to a valid 3-legged access token identifying a phone number + And the request body does not include "phoneNumber" + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 200 + And the response body complies with the OAS schema at "/components/schemas/SimSwapAgeBandInfo" + + @retrieve_age_band_7_2legged_token + Scenario: Retrieve age band using 2-legged access token + Given the header "Authorization" is set to a valid 2-legged access token + And the request body property "$.phoneNumber" is set to a valid phone number + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 200 + And the response body complies with the OAS schema at "/components/schemas/SimSwapAgeBandInfo" + + # Error scenarios + + @retrieve_age_band_401.1_no_authorization_header + Scenario: No Authorization header + Given the header "Authorization" is removed + And the request body is set to a valid request body + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 401 + And the response property "$.status" is 401 + And the response property "$.code" is "UNAUTHENTICATED" + And the response property "$.message" contains a user friendly text + + @retrieve_age_band_401.2_expired_access_token + Scenario: Expired access token + Given the header "Authorization" is set to an expired access token + And the request body is set to a valid request body + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 401 + And the response property "$.status" is 401 + And the response property "$.code" is "UNAUTHENTICATED" + And the response property "$.message" contains a user friendly text + + @retrieve_age_band_401.3_invalid_access_token + Scenario: Invalid access token + Given the header "Authorization" is set to an invalid access token + And the request body is set to a valid request body + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 401 + And the response property "$.status" is 401 + And the response property "$.code" is "UNAUTHENTICATED" + And the response property "$.message" contains a user friendly text + + @retrieve_age_band_C02.02_phone_number_not_found + Scenario: Phone number not found + Given the header "Authorization" is set to a valid access token which does not identify a single phone number + And the request body property "$.phoneNumber" is compliant with the schema but does not identify a valid phone number + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 404 + And the response property "$.status" is 404 + And the response property "$.code" is "IDENTIFIER_NOT_FOUND" + And the response property "$.message" contains a user friendly text + + @retrieve_age_band_C02.03_unnecessary_phone_number + Scenario: Phone number not to be included when it can be deduced from the access token + Given the header "Authorization" is set to a valid access token identifying a phone number + And the request body property "$.phoneNumber" is set to a valid phone number + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 422 + And the response property "$.status" is 422 + And the response property "$.code" is "UNNECESSARY_IDENTIFIER" + And the response property "$.message" contains a user friendly text + + @retrieve_age_band_400.1_invalid_phone_number + Scenario: Phone number value does not comply with the schema + Given the header "Authorization" is set to a valid access token which does not identify a single phone number + And the request body property "$.phoneNumber" does not comply with the OAS schema at "/components/schemas/PhoneNumber" + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 400 + And the response property "$.status" is 400 + And the response property "$.code" is "INVALID_ARGUMENT" + And the response property "$.message" contains a user friendly text + + @retrieve_age_band_422.1_missing_identifier + Scenario: Phone number not included and cannot be deduced from the access token + Given the header "Authorization" is set to a valid access token which does not identify a single phone number + And the request body property "$.phoneNumber" is not included + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 422 + And the response property "$.status" is 422 + And the response property "$.code" is "MISSING_IDENTIFIER" + And the response property "$.message" contains a user friendly text + + @retrieve_age_band_422.2_service_not_applicable + Scenario: Service not available for the phone number + Given that the service is not available for all phone numbers commercialized by the operator + And a valid phone number, identified by the token or provided in the request body, for which the service is not applicable + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 422 + And the response property "$.status" is 422 + And the response property "$.code" is "SERVICE_NOT_APPLICABLE" + And the response property "$.message" contains a user friendly text + + # 501 Not Implemented - operation is optional + + @retrieve_age_band_501_not_implemented + Scenario: Operation not implemented by provider + Given the provider does not implement the retrieve-age-band operation + And the request body is set to a valid request body + When the request "retrieveSimSwapAgeBand" is sent + Then the response status code is 501 + And the response property "$.status" is 501 + And the response property "$.code" is "NOT_IMPLEMENTED" + And the response property "$.message" contains a user friendly text