INDICE DEI CONTENUTI
- Documentazione API
- Abilitazione API
- API Tickets
- Metodi HTTP
- Risposta HTTP
- Creazione di una firma di autorizzazione
- Esempio di codice Java per la creazione di una firma di autorizzazione
- Calcolo della stringa ticket durante la creazione di un ticket utente
- Esempi di codice per creare un ticket come amministratore dell'organizzazione o utente
Documentazione API
Le API di Setera OneCloud possono interagire sia lato Admin che lato End User.
Puoi consultare l'elenco delle API End User dal seguente link:
Puoi consultare l'elenco delle API Admin dal seguente link:
Per l'abilitazione delle API Admin, ti preghiamo di contattare il tuo referente tecnico di Setera.
Abilitazione API
Per l'abilitazione delle API OneCloud è necessario apportare le configurazioni previste accedendo come Admin su OneCloud.
Consulta il seguente articolo su come abilitare gli utenti all'uso delle API dal portale Admin di OneCloud: [Admin] Configurazione External API Authorization
API Tickets
OneCloud utilizza un sistema di ticket per permettere l'accesso alle API. Alla creazione di un ticket verrà generato un token univoco da utilizzare come autenticazione all'uso delle API per la quale il ticket è stato concesso. I ticket vengono generati accedendo al portale utente OneCloud e avranno una durata standard di 365 giorni.
I ticket possono essere revocati se necessario.
Un ticket può essere concesso per una o molteplici API.
Autenticazione Bearer Token
L'autenticazione avviene attraverlo l'utilizzo del Token (Bearer) generato alla creazione dei Ticket.
Security Scheme Type | HTTP |
---|---|
HTTP Authorization Scheme | Bearer |
Autenticazione API Admin
Per autenticare l'accesso alle API admin, è necessario effettuare una riscrittura del metodo POST/PUT/GET/DELETE e dell'URL. La riscrittura si basa sul token di accesso e sul secret assegnati a ciascuna interfaccia API. Combinando token e secret con l'URL della richiesta, è possibile autenticare la richiesta e accedere correttamente all’API.
I seguenti parametri vengono utilizzati per l'autenticazione:
noauth_token
noauth_signature
noauth_nonce
Una firma di autorizzazione viene generata come hash MD5 di vari elementi della richiesta insieme al secret. La firma è costruita utilizzando come input il metodo, l’URL, il token e il secret.
Consulta la sezione Creazione di una firma di autorizzazione per maggiori dettagli sui parametri e sul processo.
Nomi dei parametri nella query:
Security Scheme Type | API key |
---|---|
Query parameter name | noauth_token |
Query parameter name | noauth_signature |
Query parameter name | noauth_nonce |
Metodi HTTP
Le API di OneCloud utilizzano i seguenti metodi HTTP per la manipolazione dei dati:
- GET - ottiene il dato
- POST - inserisce/crea il dato
- PUT - aggiorna il dato
- DELETE - rimuove il dato
I client HTTP che non sono in grado di utilizzare le operazioni HTTP PUT o DELETE devono tradurre le operazioni PUT e DELETE in operazioni POST con l'intestazione aggiuntiva X-HTTP-Method-Override, ad esempio:
POST /... HTTP/1.1 X-HTTP-Method-Override:PUT
Setera OneCloud risponde alla richiesta POST+X-HTTP-Method-Override come se il contenuto dell'intestazione X-HTTP-Method-Override fosse l'effettiva operazione HTTP richiesta.
Tutte le richieste al server devono avere gli header HTTP Content-Type e Accept impostati su un valore applicabile in base alla rispettiva operazione, ad esempio application/json o application/xml.
Risposta HTTP
HTTP Status Codes
Setera OneCloud restituisce i seguenti codici di risposta agli errori HTTP quando si verificano problemi:
Codice | Descrizione |
---|---|
400 | Bad Request - Quando si riceve un URI di richiesta non valido, un'intestazione HTTP non valida o un corpo del messaggio non valido. Di solito include un corpo costituito dal risultato della validazione. |
401 | Not Authorized - Quando mancano le credenziali di autorizzazione o non si è autorizzati ad accedere alla risorsa richiesta. |
403 | Not Authorized - Il server ha compreso la richiesta ma si rifiuta di soddisfarla. L'autorizzazione non è utile. Il contesto di autorizzazione corrente non consente la richiesta. |
404 | Not Found - Il server non trova la risorsa che corrisponde all'URI richiesto. |
405 | Method Not Allowed - Il metodo specificato nella richiesta non è consentito per la risorsa identificata dalla richiesta. |
409 | Conflict - La richiesta non può essere completata a causa di un conflitto con lo stato attuale della risorsa. Questo codice è consentito solo in situazioni in cui si prevede che l'utente possa risolvere il conflitto e inviare nuovamente la richiesta. |
500 | Internal Server Error - È il codice predefinito utilizzato per tutti gli errori non riconosciuti. |
501 | Not Implemented - La richiesta era valida ma non è ancora stata implementata da Setera OneCloud. |
Setera OneCloud può aggiungere un'intestazione Warning con codice 703 alla risposta in caso di errori. Il testo dell'intestazione conterrà un token di log nella forma "Error occurred, see {token}".
Creazione di una firma di autorizzazione
La codifica URL può essere eseguita secondo diversi RFC. Per calcolare correttamente le firme, è necessario seguire l’RFC 3986, il che significa, ad esempio, che lo spazio deve essere codificato come %20
(e non come +
).
Se dobbiamo autenticare il seguente URL:
http://mn.telepo.org/api/admin/user/sn1.com?query=alice%20with%20space
, bisogna seguire i seguenti passaggi:
Creare un valore casuale per il nonce. Può essere una stringa codificata in base64 o esadecimale.
Ordinare tutte le coppie chiave-valore dei parametri, escludendo
noauth_signature
, in ordine alfabetico secondo il nome del parametro:noauth_nonce,noauth_token,query
Concatenare tutti i valori dei parametri, utilizzando
&
come delimitatore e=
tra nome e valore. I valori non devono essere codificati in URL in questa fase (es. lo spazio deve rimanere uno spazio):noauth_nonce=fd1938e6&noauth_token=1.VDowODQ2NGU5MDRmNzQzYmQz&query=alice with space
Codificare l'intero URL (senza la parte di query) utilizzando la codifica secondo l’RFC 3986.
Codificare i parametri concatenati utilizzando sempre la codifica dell’RFC 3986.
Concatenare metodo, URL codificato, stringa dei parametri codificata e secret del token usando
&
come delimitatore:GET&http%3A%2F%2Fmn.telepo.org%2Fapi%2Fadmin%2Fuser%2Fsn1.com&noauth_nonce%3Dfd1938e6%26noauth_token%3D1.VDowODQ2NGU5MDRmNzQzYmQz%26query%3Dalice%20with%20space&f936c1ed0c1c570c
Applicare l’hash MD5 alla stringa risultante e convertirla in rappresentazione esadecimale. Questo sarà il valore di
noauth_signature
, da aggiungere all’URL finale:https://mn.telepo.org/api/admin/user/sn1.com?query=alice%20with%20space&noauth_token=1.VDowODQ2NGU5MDRmNzQzYmQz&noauth_nonce=fd1938e6&noauth_signature=4ce4cb4765bd0415d75c7d06b7e0f75a
Esempio di codice Java per la creazione di una firma di autorizzazione
package com.telepo.api; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.net.URLEncoder; import java.net.URLDecoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Map; import java.util.Random; import java.util.TreeMap; import java.util.Map.Entry; publicclass Signer { privatefinalstatic String NONCE="noauth_nonce"; privatefinalstatic String TOKEN="noauth_token"; privatefinalstatic String SIGNATURE="noauth_signature"; privatefinalstatic String ENCODING="UTF-8"; publicstatic String generateNonce(){ Random generator = new Random(System.currentTimeMillis()); return Integer.toHexString(generator.nextInt()); } publicstatic Map<String, String> mapParams(String queryString){ try { Map<String, String> query = new TreeMap<String, String>(); for(String param : queryString.split("&")){ String[] keyValue = param.split("="); if (query.containsKey(keyValue[0])) { // handle multiple values String string = query.get(keyValue[0]); string = string + "&" + keyValue[0] + "=" + keyValue[1]; query.put(keyValue[0], string); } else { String value = URLDecoder.decode(keyValue[1], "UTF-8"); query.put(keyValue[0], value); } } return query; } catch (UnsupportedEncodingException e) { thrownew RuntimeException(e); } } publicstatic String pad(String s, int length, char pad) { StringBuffer buffer = new StringBuffer(s); while (buffer.length() < length) { buffer.insert(0, pad); } return buffer.toString(); } publicstatic String generateMD5Hash(String input) throws NoSuchAlgorithmException, UnsupportedEncodingException{ MessageDigest digester = MessageDigest.getInstance("MD5"); digester.update(input.getBytes(ENCODING)); return pad( (new BigInteger(1,digester.digest())).toString(16),32,'0'); } publicstatic String getSignedUrl(String method, String url, String token, String secret){ int queryIdx = url.indexOf('?'); String baseUrl = url; String queryString = null; if( queryIdx != -1){ baseUrl = url.substring(0,queryIdx); queryString = url.substring(queryIdx+1, url.length()); } String nonce = generateNonce(); Map<String, String> parameters = new TreeMap<String,String>(); parameters.put(TOKEN,token); parameters.put(NONCE, nonce); if(queryString != null){ Map<String, String> query = mapParams(queryString); parameters.putAll(query); } StringBuilder queryBuilder = new StringBuilder(); for(Entry<String, String> entry : parameters.entrySet()){ if(queryBuilder.length() > 0){ queryBuilder.append("&"); } queryBuilder.append(entry.getKey()); queryBuilder.append("="); queryBuilder.append(entry.getValue()); } try { String encodedBaseUrl = URLEncoder.encode(baseUrl, ENCODING); String encodedQueryString = URLEncoder.encode(queryBuilder.toString(), ENCODING); // RFC 3986 style encoding encodedQueryString = encodedQueryString.replace("+", "%20").replace("*", "%2A"). replace("%7E", "~").replace("%2C", "."); String signatureString = method.toUpperCase() + "&" + encodedBaseUrl + "&" + encodedQueryString + "&" + secret; String signature = generateMD5Hash(signatureString); String firstDelim = queryIdx == -1 ? "?" : "&"; return url + firstDelim + TOKEN + "=" + token + "&" + NONCE + "=" + nonce + "&" + SIGNATURE + "=" + signature; } catch (UnsupportedEncodingException e) { thrownew RuntimeException(e); } catch (NoSuchAlgorithmException e) { thrownew RuntimeException(e); } } }
Calcolo della stringa ticket durante la creazione di un ticket utente
La stringa ticket viene utilizzata per creare i ticket utente.
La costruzione della stringa avviene come segue:
t=Dbase64({domain}).base64(P:{hash}:{userId}[:{value}:…])
Dove:
base64(string)
– restituisce la stringa codificata in base64.
Per rendere la stringa compatibile con gli URL (URI-safe), è necessario:
Rimuovere il padding della codifica base64 (
=
alla fine)Sostituire i caratteri
+
e/
con-
e_
rispettivamente
hash – è un hash md5 restituito come stringa esadecimale
md5(userId[:{value}:…]:{secret})
Dove secret
è una rappresentazione esadecimale di:
md5({userId}:{domain}:{password})
Esempio: creazione di una stringa ticket con permessi per USER e CONTACT
t = 'D' + base64(domain) + '.' + base64("P:" + md5(userId + allowedApis + ':' + secret) +':' + userId + allowedApis) secret = md5(userId + ':' + domain + ':' + password) allowedApis = ":USER:CONTACT"
Esempi di codice per creare un ticket come amministratore dell'organizzazione o utente
Prima di eseguire i codici negli esempi sotto riportati, modificare i valori di:
extAddr
,user
,orgAdmOrSameAsUser
,domain
,password
, ed eventualmente la listaallowedApis
.
Java
TokenDemo.java
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import javax.net.ssl.HttpsURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; publicclass TokenDemo { privatestatic String getHex(byte[] array) { StringBuilder sb = new StringBuilder(); for (byte b : array) { sb.append(String.format("%02x", b & 0xff)); } return sb.toString(); } privatestatic String getMd5(String input) { if (input != null) { try { return getHex(MessageDigest.getInstance("MD5").digest(input.getBytes(StandardCharsets.UTF_8))); } catch (NoSuchAlgorithmException ignored) { } } returnnull; } privatestatic String getHashedPassword(String userId, String domain, String password) { return getMd5(userId + ':' + domain + ':' + password); } privatestatic String getHash(String userId, String[] allowedApis, String password) { return getMd5(userId + ':' + String.join(":", allowedApis) + ':' + password); } privatestatic String getBase64(String input) { return Base64.getEncoder().encodeToString(input.getBytes(StandardCharsets.UTF_8)).replaceAll("=", ""); } privatestatic String getBase64Domain(String input) { return Base64.getEncoder().encodeToString(input.getBytes(StandardCharsets.UTF_8)); } privatestatic String getTicketString(String orgAdmOrSameAsUser, String hash, String domain, String[] allowedApis) { String tvalue = orgAdmOrSameAsUser + ':' + String.join(":", allowedApis); return'D' + getBase64Domain(domain) + '.' + getBase64("P:" + hash + ':' + tvalue); } publicstatic String getUrl(String extAddr, String orgAdmOrSameAsUser, String hash, String domain, String user, String[] allowedApis) { return extAddr + "/api/tickets/" + domain + "/" + user + "?platform=other" + "&api=" + String.join("&api=", allowedApis) + "&name=CallTicket&t=" + getTicketString(orgAdmOrSameAsUser, hash, domain, allowedApis); } publicstaticvoid main(String[] args) throws IOException { String extAddr = "https://bcs.mydomain.com"; String user = "john"; String orgAdmOrSameAsUser = "jane"; // Jane is orgadmin String domain = "first.org"; String password = "password1"; String[] allowedApis = new String[]{"CALLS", "CALL_CONTROL"}; String hashedPassword = getHashedPassword(orgAdmOrSameAsUser, domain, password); String hash = getHash(orgAdmOrSameAsUser, allowedApis, hashedPassword); URL url = new URL(getUrl(extAddr, orgAdmOrSameAsUser, hash, domain, user, allowedApis)); System.out.println("user : " + user); System.out.println("creator : " + (!user.equals(orgAdmOrSameAsUser) ? "orgadmin " : "") + orgAdmOrSameAsUser); System.out.println("hash : " + hash); System.out.println("curl cmd : " + "curl -X POST -H"Accept: application/json" "" + url + """); HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); con.setRequestMethod("POST"); con.setRequestProperty("Accept", "application/json"); try { System.out.println("response : " + con.getResponseCode()); BufferedReader in=new BufferedReader(new InputStreamReader(con.getInputStream())); String inputLine; StringBuilder response = new StringBuilder(); while ((inputLine = in.readLine()) != null) { response.append(inputLine); } in.close(); System.out.println(response.toString().replaceAll("{"", "{n "") .replaceAll(""([ta])", "n "$1") .replaceAll("}", "n}")); } catch (IOException ie) { System.out.println("cause : " + ie.getMessage()); } } }
Node.js
tokendemo.js
var md5 = require('md5'); var request = require('request'); var getBase64 = function (s) { returnnew Buffer(s).toString('base64').replace(/=/g, ''); }; var getBase64Domain = function (s) { returnnew Buffer(s).toString('base64'); }; var getTicketString = function (orgAdmOrSameAsUser, hash, domain, allowedApis) { var tmpStr = orgAdmOrSameAsUser + ':' + allowedApis.join(':'); return'D'+getBase64Domain(domain)+'.'+ getBase64('P:' + hash + ':' + tmpStr); }; var getUrl = function (extAddr, orgAdmOrSameAsUser, hash, domain, allowedApis, user) { return extAddr + '/api/tickets/' + domain + '/' + user + '?platform=other' + '&api=' + allowedApis.join('&api=') + '&name=CallTicket&t=' + getTicketString(orgAdmOrSameAsUser, hash, domain, allowedApis); }; var allowedApis = ['CALLS', 'CALL_CONTROL']; var extAddr = 'https://bcs.mydomain.com'; var domain = 'first.org'; var user = 'john'; var orgAdmOrSameAsUser = 'jane'; // Jane is orgadmin var password = 'password1'; // use generated hash instead of plain password var hashedPwd = md5(orgAdmOrSameAsUser + ':' + domain + ':' + password); var hash = md5(orgAdmOrSameAsUser+ ':' + allowedApis.join(':') + ':' + hashedPwd) var url = getUrl(extAddr, orgAdmOrSameAsUser, hash, domain, allowedApis, user); console.log('nuser : ' + user); console.log('creator : ' + orgAdmOrSameAsUser); console.log('hash : ' + hash); console.log('curl cmd : '+'curl -X POST -H"Accept: application/json" "'+url+'"n'); request({ url: url, method: 'POST', json: true, headers: {'Accept': 'application/json'} }, function (error, response, body) { if (!error && response.statusCode === 200) { console.log('name : ' + body.name); console.log('token : ' + body.token); console.log('for apis : ' + allowedApis.join(' ') + 'n'); } else { console.log('errorCode : ' + response.statusCode); console.log(response.body); } }); package.json: { "name": "ticket-demo", "version": "0.0.2", "private": true, "dependencies": { "md5": "^2.1.0", "request": "^2.72.0" } }
Python 3.x
import base64 import hashlib import requests class TokenDemo: @staticmethod def get_hex(byte_array): return ''.join(f'{b:02x}' for b in byte_array) @staticmethod def get_md5(input_str): if input_str: return TokenDemo.get_hex(hashlib.md5(input_str.encode('utf-8')).digest()) returnNone @staticmethod def get_hashed_password(user_id, domain, password): return TokenDemo.get_md5(f"{user_id}:{domain}:{password}") @staticmethod def get_hash(user_id, allowed_apis, password): return TokenDemo.get_md5(f"{user_id}:{':'.join(allowed_apis)}:{password}") @staticmethod def get_base64(input_str): return base64.b64encode(input_str.encode('utf-8')).decode('utf-8').replace("=", "") @staticmethod def get_base64_domain(input_str): return base64.b64encode(input_str.encode('utf-8')).decode('utf-8') @staticmethod def get_ticket_string(org_adm_or_user, hash_str, domain, allowed_apis): tvalue = f"{org_adm_or_user}:{':'.join(allowed_apis)}" return f"D{TokenDemo.get_base64_domain(domain)}.{TokenDemo.get_base64(f'P:{hash_str}:{tvalue}') }" @staticmethod def get_url(ext_addr, org_adm_or_user, hash_str, domain, user, allowed_apis): return (f"{ext_addr}/api/tickets/{domain}/{user}?platform=other" f"&api={'&api='.join(allowed_apis)}&name=CallTicket" f"&t={TokenDemo.get_ticket_string(org_adm_or_user, hash_str, domain, allowed_apis)}") if __name__ =="__main__": ext_addr ="https://bcs.mydomain.com" user ="john" org_adm_or_same_as_user ="jane"# jane is orgadmin domain ="first.org" allowed_apis = ["CALLS", "CALL_CONTROL"] password = TokenDemo.get_hashed_password("jane", domain, "RjECkqDk7oz7tT") hash_str = TokenDemo.get_hash(org_adm_or_same_as_user, allowed_apis, password) url = TokenDemo.get_url(ext_addr, org_adm_or_same_as_user, hash_str, domain, user, allowed_apis) print(f"user: {user}") print(f"creator: {'orgadmin ' if user != org_adm_or_same_as_user else ''}{org_adm_or_same_as_user}") print(f"hash: {hash_str}") print(f"curl cmd: curl -X POST -H"Accept: application/json" "{url}"") try: response = requests.post(url, headers={"Accept": "application/json"}) print(f"response: {response.status_code}") print(response.json()) except requests.RequestException as e: print(f"cause: {str(e)}")
Questa risposta ti è stata utile?
Fantastico!
Grazie per il tuo feedback
Siamo spiacenti di non poterti essere di aiuto
Grazie per il tuo feedback
Feedback inviato
Apprezziamo il tuo sforzo e cercheremo di correggere l’articolo