diff --git a/catalog/qod/qod.md b/catalog/qod/qod.md index 8d40d78..5bfd1fc 100644 --- a/catalog/qod/qod.md +++ b/catalog/qod/qod.md @@ -1,5 +1,5 @@ --- -title: QoD +title: Quality on Demand API excerpt: The QoD API allows the developer to prioritize network traffic on certain devices on demand. category: 66aa4f941e51e7000fa353ce --- diff --git a/catalog/qod/samplecode_qod.md b/catalog/qod/samplecode_qod.md new file mode 100644 index 0000000..42629be --- /dev/null +++ b/catalog/qod/samplecode_qod.md @@ -0,0 +1,529 @@ +--- +title: Sample code for QoD +excerpt: The following samples show how to use the [Open Gateway Quality on Demand API](https://opengateway.telefonica.com/en/apis/qod-mobile) to optimize your user's mobile connectivity by creating sessions tailored to the type of network traffic generated by your app, and trigger it from the app itself, or from a backend on use cases where the end-devices don't run your app (e.g. drones, cameras, etc.). +category: 66aa4f941e51e7000fa353ce +--- + +> 📘 Note +> +> To try out our APIs, visit the [Sandbox](https://opengateway.telefonica.com/developer-hub/unirse). + +The following code shows, for didactic purposes, a hypothetical SDK, in several programming languages, from a generic Open Gateway's channel partner, also known as aggregator. The final implementation will depend on the channel partner's development tools offering. Note that channel partners' Open Gateway SDKs are just code modules wrapping authentication and API calls providing an interface in your app's programming for convenience. + +Sample code on how to consume the API without an SDK, directly with HTTP requests, is also provided, and it is common and valid no matter what your partner is, thanks to the CAMARA standardization. If you do not use an SDK you need to code the HTTP calls and additional stuff like encoding your credentials, calling authorization endpoints, handling tokens, etc. You can check our sample [Postman collection](https://bxbucket.blob.core.windows.net/bxbucket/opengateway-web/uploads/OpenGateway.postman_collection.json) as a reference. + +> 📘 It is recommended to use the [API Reference tool](https://developers.opengateway.telefonica.com/reference/) for faster calls of our APIs + +### Table of contents +- [Backend flow](#backend-flow) + - [Authentication](#authentication) + - [API usage](#api-usage) +- [Frontend flow](#frontend-flow) + - [Requesting the authorization code from the frontend](#requesting-the-authorization-code-from-the-frontend) + - [Getting the access token from the auth callback endpoint at the backend](#getting-the-access-token-from-the-auth-callback-endpoint-at-the-backend) + - [API Usage](#api-usage-1) + +## Code samples +> 📘 These are code examples +> - Remember to replace 'my-app-id' and 'my-app-secret' with the credentials of your app. (If you are using our Sandbox, you can get them [here](https://sandbox.opengateway.telefonica.com/my-apps)). +> - Remember also to replace "aggregator/opengateway-sdk" with the SDK from your aggregator. If you are using our sandbox SDK, check info and installation of de Sandbox SDK [here](../../gettingstarted/sandbox/sdkreference.md) + +### Backend flow + +A lot of use cases for the Quality on Demand API are based on optimizing the connectivity of non human-interface devices, such as drones or IP cameras. In those cases, your application will be managing such devices and perform the API consumption from the backend, not from the device itself. + + The authentication protocol used in Open Gateway for backend flows is the OIDC standard CIBA (Client Initiated Backchannel Authentication). You can check the CAMARA documentation on this flow [here](https://github.com/camaraproject/IdentityAndConsentManagement/blob/release-0.1.0/documentation/CAMARA-API-access-and-user-consent.md#ciba-flow-backend-flow). + +First step is to instantiate the Quality on Demand service class included in the corresponding SDK. By providing your app's credentials to the class constructor, it handles the CIBA authentication on its behalf. Providing the device IP address as well, as an identifier of the line to be optimized, will let your app to just effectively use the API in a single line of code below. + +#### Authentication + +Since Open Gateway authentication is 3-legged, meaning it identifies the application, the operator and the operator's mobile line providing the device with connectivity, each operation on a different device needs its own SDK class instantiation with its IP address, or access token if not using an SDK. + +##### [TAB] SDK for Node.js +```Node +import { QoDMobile, ClientCredentials, QoSProfiles } from "aggregator/opengateway-sdk" + +const credentials: ClientCredentials( + clientId: 'my-app-id', + clientSecret: 'my-app-secret' +) + +let deviceIpPortAddress = getDeviceIP() // e.g. '203.0.113.25:8080' + +const qodClient = new QoDMobile(credentials, null, deviceIpPortAddress) +``` + +##### [TAB] SDK for Java +```Java +import aggregator.opengatewaysdk.ClientCredentials; +import aggregator.opengatewaysdk.QoDMobile; +import aggregator.opengatewaysdk.QoSProfiles; + +ClientCredentials credentials = new ClientCredentials( + "my-app-id", + "my-app-secret" +); + +String deviceIpPortAddress = this.getDeviceIP(); // e.g. "203.0.113.25:8080" + +QoDMobile qodClient = new QoDMobile(credentials, null, deviceIpPortAddress); +``` + +##### [TAB] SDK for Python +```Python +from aggregator_opengateway_sdk import QoDMobile, ClientCredentials, QoSProfiles + +credentials = ClientCredentials( + clientid='my-app-id', + clientsecret='my-app-secret' +) + +device_ip_address = self.get_device_ip() # e.g. '203.0.113.25:8080' + +qod_client = QoDMobile(client=credentials, ip_address=device_ip_address) +``` + +##### [TAB] HTTP using JavaScript (ES6) +```JavaScript +// First step: +// Perform an authorization request + +let deviceIpPortAddress = getDeviceIP(); // e.g. '203.0.113.25:8080' + +let clientId = "my-app-id"; +let clientSecret = "my-app-secret"; +let appCredentials = btoa(`${clientId}:${clientSecret}`); +let apiPurpose = "dpv:RequestedServiceProvision#qod"; + +const myHeaders = new Headers(); +myHeaders.append("Content-Type", "application/x-www-form-urlencoded"); +myHeaders.append("Authorization", `Basic ${appCredentials}`); + +const urlencoded = new URLSearchParams(); +urlencoded.append("login_hint", `ipport:${deviceIpPortAddress}`); +urlencoded.append("purpose", apiPurpose); + +const requestOptions = { + method: "POST", + headers: myHeaders, + body: urlencoded +}; + +let authReqId; + +fetch("https://opengateway.aggregator.com/bc-authorize", requestOptions) + .then(response => response.json()) + .then(result => { + authReqId = result.auth_req_id; + }) + +// Second step: +// Requesting an access token with the auth_req_id included in the result above + +const myHeaders = new Headers(); +myHeaders.append("Content-Type", "application/x-www-form-urlencoded"); +myHeaders.append("Authorization", `Basic ${appCredentials}`); + +const urlencoded = new URLSearchParams(); +urlencoded.append("grant_type", "urn:openid:params:grant-type:ciba"); +urlencoded.append("auth_req_id", authReqId); + +const requestOptions = { + method: "POST", + headers: myHeaders, + body: urlencoded +}; + +let accessToken; + +fetch("https://opengateway.aggregator.com/token", requestOptions) + .then(response => response.json()) + .then(result => { + accessToken = result.access_token; + }) +``` + +##### [TAB] HTTP using Java +```Java +// First step: +// Perform an authorization request + +String deviceIpPortAddress = this.getDeviceIP(); // e.g. "203.0.113.25:8080" + +String clientId = "my-app-id"; +String clientSecret = "my-app-secret"; +String appCredentials = clientId + ":" + clientSecret; +String credentials = Base64.getEncoder().encodeToString(appCredentials.getBytes(StandardCharsets.UTF_8)); +String apiPurpose = "dpv:RequestedServiceProvision#qod"; + +HttpClient client = HttpClient.newHttpClient(); + +Map data = new HashMap<>(); +data.put("login_hint", "ipport:" + deviceIpPortAddress); +data.put("purpose", apiPurpose); + +StringBuilder requestBody = new StringBuilder(); +for (Map.Entry entry : data.entrySet()) { + if (requestBody.length() > 0) { + requestBody.append("&"); + } + requestBody.append(URLEncoder.encode(entry.getKey().toString(), StandardCharsets.UTF_8)); + requestBody.append("="); + requestBody.append(URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8)); +} + +HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://opengateway.aggregator.com/bc-authorize")) + .header("Content-Type", "application/x-www-form-urlencoded") + .header("Authorization", "Basic " + credentials) + .POST(BodyPublishers.ofString(requestBody.toString())) + .build(); + +HttpResponse response = client.send(request, BodyHandlers.ofString()); +JSONObject jsonResponse = new JSONObject(response.body); +String authReqId = jsonResponse.getString("auth_req_id"); + +// Second step: +// Requesting an access token with the auth_req_id included in the result above + +Map data = new HashMap<>(); +data.put("grant_type", "urn:openid:params:grant-type:ciba"); +data.put("auth_req_id", authReqId); + +StringBuilder requestBody = new StringBuilder(); +for (Map.Entry entry : data.entrySet()) { + if (requestBody.length() > 0) { + requestBody.append("&"); + } + requestBody.append(URLEncoder.encode(entry.getKey().toString(), StandardCharsets.UTF_8)); + requestBody.append("="); + requestBody.append(URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8)); +} + +HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://opengateway.aggregator.com/token")) + .header("Content-Type", "application/x-www-form-urlencoded") + .header("Authorization", "Basic " + credentials) + .POST(BodyPublishers.ofString(requestBody.toString())) + .build(); + +HttpResponse response = client.send(request, BodyHandlers.ofString()); +JSONObject jsonResponse = new JSONObject(response.body()); +String accessToken = jsonResponse.getString("access_token"); +``` + +##### [TAB] HTTP using Python +```Python +# First step: +# Perform an authorization request + +device_ip_address = self.get_device_ip() # e.g. '203.0.113.25:8080' + +client_id = "my-app-id" +client_secret = "my-app-secret" +app_credentials = f"{client_id}:{client_secret}" +credentials = base64.b64encode(app_credentials.encode('utf-8')).decode('utf-8') +api_purpose = "dpv:RequestedServiceProvision#qod" + +headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": f"Basic {credentials}" +} + +data = { + "login_hint": f"ipport:{device_ip_address}", + "purpose": api_purpose +} + +response = requests.post( + "https://opengateway.aggregator.com/bc-authorize", + headers=headers, + data=data +) + +auth_req_id = response.json().get("auth_req_id") + +# Second step: +# Requesting an access token with the auth_req_id included in the result above + +headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": f"Basic {credentials}" +} + +data = { + "grant_type": "urn:openid:params:grant-type:ciba", + "auth_req_id": auth_req_id +} + +response = requests.post( + "https://opengateway.aggregator.com/token", + headers=headers, + data=data +) + +access_token = response.json().get("access_token") +``` + +#### API usage + +Once your app is authenticated it only takes a single line of code to use the service API and effectively optimize device connectivity. + + +##### [TAB] SDK for Node.js +```Node +const duration = 300 // Seconds + +qodClient.setQualityOfService(duration, QoSProfiles.QOS_E) +``` + +##### [TAB] SDK for Java +```Java +int duration = 300; // Seconds + +qodClient.setQualityOfService(duration, QoSProfiles.QOS_E); +``` + +##### [TAB] SDK for Python +```Python +duration = 300 # Seconds + +qod_client.set_quality(duration, QoSProfiles.QOS_E) +``` + +##### [TAB] HTTP using JavaScript (ES6) +```JavaScript +const myHeaders = new Headers(); +myHeaders.append("Content-Type", "application/json"); +myHeaders.append("Authorization", `Bearer ${accessToken}`); + +const requestBody = JSON.stringify({ + "device": { + "ipv4Address": { + "publicAddress": "203.0.113.25", + "publicPort": 8080 + }, + "applicationServer": { + "ipv4Address": "0.0.0.0/0" + }, + "qosProfile": "QOS_E", + "duration": 300 + } +}); + +const requestOptions = { + method: "POST", + headers: myHeaders, + body: requestBody +}; + +fetch("https://opengateway.aggregator.com/qod/v0/sessions", requestOptions) + .then(response => response.json()) + .then(result => { + console.log(`Session created with id: ${result.sessionId}`) + }) +``` + +##### [TAB] HTTP using Java +```Java +JSONObject requestBody = new JSONObject(); +JSONObject device = new JSONObject(); +JSONObject ipv4Address = new JSONObject(); +ipv4Address.put("publicAddress", "203.0.113.25"); +ipv4Address.put("publicPort", 8080); +device.put("ipv4Address", ipv4Address); +JSONObject applicationServer = new JSONObject(); +applicationServer.put("ipv4Address", "0.0.0.0/0"); +device.put("applicationServer", applicationServer); +device.put("qosProfile", "QOS_E"); +device.put("duration", 300); +requestBody.put("device", device); + +HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://opengateway.aggregator.com/qod/v0/sessions")) + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + accessToken) + .POST(BodyPublishers.ofString(requestBody.toString())) + .build(); + +HttpResponse response = client.send(request, BodyHandlers.ofString()); +JSONObject jsonResponse = new JSONObject(response.body()); +String sessionId = jsonResponse.getString("sessionId"); + +System.out.println("Session created with id: " + sessionId); +``` + +##### [TAB] HTTP using Python +```Python +headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {access_token}" +} + +data = { + "device": { + "ipv4Address": { + "publicAddress": "203.0.113.25", + "publicPort": 8080 + }, + "applicationServer": { + "ipv4Address": "0.0.0.0/0" + }, + "qosProfile": "QOS_E", + "duration": 300 + } +} + +response = requests.post( + "https://opengateway.aggregator.com/qod/v0/sessions", + headers=headers, + data=json.dumps(data) +) + +result = response.json() +print(f"Session created with id: {result.get('sessionId')}") +``` + +### Frontend flow + +The recommended API consumption flow when triggered from the frontend application, running on the end-user device, is implementing the OIDC standard Authorization Code Flow for authentication: + +You can check the CAMARA documentation on this flow [here](https://github.com/camaraproject/IdentityAndConsentManagement/blob/release-0.1.0/documentation/CAMARA-API-access-and-user-consent.md#authorization-code-flow-frontend-flow). +* Application's frontend performs an HTTP request to get a `code`, and provides a `redirect_uri` it wants such `code` to be redirected to. +* Application's frontend will receive an HTTP redirect (status 302) and needs to be able to handle it. If it is a web application running on a web browser, the browser will natively follow the redirection. If it is not, in depends on the coding language and/or HTTP module or library used, or on its settings, how the flow will follow all the way to your application's backend through the mobile network operator authentication server. +* Application's backend receives the `code` from this HTTP redirection, by publishing an endpoint in the given `redirect_uri`, and then exchanges it for an access token. The latter is achieved as shown in the [Backend](#backend) flow. + +#### Requesting the authorization code from the frontend + +The following samples show how your application can trigger the authentication flow from the frontend either from code or by submitting a simple HTML form. The same can be achieved from code in any other programming language with the ability to perform HTTP requests: + +##### [TAB] HTTP using JavaScript (ES6) +```JavaScript +let clientId = "my-app-id"; +let clientSecret = "my-app-secret"; +let apiPurpose = "dpv:RequestedServiceProvision#qod"; +let myAuthCallbackEndpoint = "https://my_app_server/qod-auth-callback"; + +const params = { + client_id: clientId, + response_type: "code", + purpose: apiPurpose, + redirect_uri: myAuthCallbackEndpoint +}; + +// Create the query string +const queryString = new URLSearchParams(params).toString(); + +// URL with query string +const url = `https://opengateway.aggregator.com/authorize?${queryString}`; + +const requestOptions = { + method: "GET", + redirect: "follow" +}; + +fetch(url, requestOptions); +``` + +##### [TAB] HTML form +```HTML + + + + + + + + API Request Form + + +

Quality on Demand session creation

+
+ + + + + + + +
+ + +``` + +#### Getting the access token from the auth callback endpoint at the backend + +Samples represent how to publish the auth callback URL in Python or Node.js, so the code from the Auth Code Flow can be received. The same can be achieved in any other language with capabilities to run an HTTP server and listen for the redirect from the authentication flow: + +##### [TAB] SDK for Python +```Python +from flask import Flask, request, jsonify +from aggregator_opengateway_sdk import ClientCredentials, QoDMobile, QoSProfiles + +credentials = ClientCredentials( + clientid='my-app-id', + clientsecret='my-app-secret' +) + +app = Flask(__name__) + +@app.route('/qod-auth-callback', methods=['GET']) +def auth_callback(): + code = request.args.get('code', '') + qod_client = QoDMobile(client=credentials, auth_code=code) + +if __name__ == '__main__': + app.run() +``` + +##### [TAB] SDK for Node.js +```Node +import { ClientCredentials, QoDMobile } from "aggregator/opengateway-sdk" +import express from "express" + +const credentials: ClientCredentials( + clientId: 'my-app-id', + clientSecret: 'my-app-secret' +) + +const app = express() +const port = 3000 + +app.get('/qod-auth-callback', (req, res) => { + const code = req.query.code + const qodClient = new QoDMobile(credentials, code) +}) + +app.listen(port, () => { + console.log(`QoD authorization callback URL is running`); +}) +``` + +#### API Usage + +Once we have instantiated the SDK class with the authorization code received from the frontend, we can just call the class service function to effectively create a QoD session for the frontend device, by getting its IP address from the HTTP request header. + +For that, still in the code of the auth callback URL endpoint listener, following to the QoDMobile class instantiation, follow the samples below: + +##### [TAB] SDK for Python +```Python + ip = request.remote_addr + port = request.environ.get('REMOTE_PORT') + + qod_client.set_quality(300, QoSProfiles.QOS_E, client_ip=ip, client_port=port) +``` + +##### [TAB] SDK for Node.js +```Node + const ip = req.ip + const port = req.socket.remotePort + + qodClient.setQualityOfService(300, QoSProfiles.QOS_E, ip, port) +``` + +In case of not using an SDK, at this point, [the samples for the backend flow](#api-usage) works for the frontend flow once we have gotten the frontend device IP address from the HTTP request header.